Я работаю над системой списков желаний, где пользователи могут добавлять элементы в свои списки желаний, и я планирую разрешить пользователям в дальнейшем изменять их порядок. Я не совсем уверен, как лучше сохранить это в базе данных, оставаясь быстрым и не превращаясь в беспорядок (это приложение будет использоваться довольно большой базой пользователей, поэтому я не хочу, чтобы оно было закрыто убирать вещи).
Сначала я пробовал position
столбец, но, похоже, было бы неэффективно менять значение позиции каждого другого элемента при его перемещении.
Я видел людей, использующих собственную ссылку для ссылки на предыдущее (или следующее) значение, но, опять же, кажется, что вам придется обновить множество других элементов в списке.
Другое решение, которое я видел, - это использование десятичных чисел и просто вставление элементов в промежутки между ними, что пока кажется лучшим решением, но я уверен, что должен быть лучший способ.
Я бы сказал, что типичный список будет содержать около 20 или около того элементов, и я, вероятно, ограничу его до 50. Переупорядочение будет использовать перетаскивание и, вероятно, будет сделано в пакетном режиме, чтобы предотвратить условия гонки и тому подобное от Аякс просит. Я использую postgres (на героку), если это имеет значение.
У кого-нибудь есть какие-либо идеи?
Приветствия за любую помощь!
источник
Ответы:
Во-первых, не пытайтесь делать что-то умное с десятичными числами, потому что они вас злят.
REAL
иDOUBLE PRECISION
неточны и могут неправильно представлять то, что вы вкладываете в них.NUMERIC
это точно, но правильная последовательность шагов изменит вашу точность, и ваша реализация будет плохо работать.Ограничение количества движений одним взлетом и падением делает всю операцию очень простой. Чтобы получить список последовательно пронумерованных элементов, вы можете переместить элемент вверх, уменьшив его позицию и увеличивая номер позиции независимо от того, что придумал предыдущий декремент. (Другими словами, предмет
5
станет,4
а то, что было предметом,4
станет5
обменом, как описал Моронс в своем ответе.) Сдвиг его будет противоположным. Индексируйте свою таблицу по уникальному идентификатору списка и позиции, и вы можете сделать это с двумяUPDATE
s внутри транзакции, которая будет выполняться очень быстро. Если ваши пользователи не перестраивают свои списки на сверхчеловеческих скоростях, это не вызовет большой нагрузки.Перетаскивание (например, перемещение элемента,
6
чтобы сидеть между элементами9
и10
) немного сложнее и должно выполняться по-разному в зависимости от того, находится ли новая позиция выше или ниже старой. В приведенном выше примере вы должны открыть отверстие, увеличивая все позиции больше чем9
, обновляя позицию элемента6
, чтобы она стала новой,10
а затем уменьшая позицию на все, что больше, чем6
заполнить освободившееся место. С тем же индексированием, которое я описал ранее, это будет быстро. На самом деле вы можете сделать это немного быстрее, чем я описал, уменьшив количество строк, к которым обращается транзакция, но это микрооптимизация, в которой вам не нужно, пока вы не докажете, что есть узкое место.В любом случае, попытка превзойти базу данных с помощью самодельного, слишком умного на половину решения обычно не приводит к успеху. Базы данных, достойные их соли, были тщательно написаны для выполнения этих операций очень, очень быстро людьми, которые очень, очень хороши в этом.
источник
Тот же ответ здесь https://stackoverflow.com/a/49956113/10608
Решение: создать
index
строку (потому что строки, по сути, имеют бесконечную «произвольную точность»). Или, если вы используете int, увеличьтеindex
на 100 вместо 1.Проблема производительности заключается в следующем: между двумя отсортированными элементами нет промежуточных значений.
Вместо этого сделайте так (лучшее решение ниже):
Еще лучше: вот как Джира решает эту проблему. Их «ранг» (то, что вы называете индексом) - это строковое значение, которое дает тонну передышки между ранжированными предметами.
Вот реальный пример базы данных jira, с которой я работаю
Обратите внимание на этот пример
hzztzz:i
. Преимущество строкового ранга в том, что вам не хватает места между двумя предметами, вам все равно не нужно переоценивать что-либо еще. Вы просто начинаете добавлять больше символов в строку, чтобы сузить фокус.источник
Почему? Предположим, вы используете подход со связанными таблицами со столбцами (listID, itemID, nextItemID).
Вставка нового элемента в список стоит одну вставку и одну измененную строку.
Перемещение элемента стоит три модификации строки (перемещаемый элемент, элемент перед ним и элемент перед новым местоположением).
Удаление элемента стоит одно удаление и одна измененная строка.
Эти затраты остаются неизменными независимо от того, есть ли в списке 10 пунктов или 10 000 пунктов. Во всех трех случаях есть на одну модификацию меньше, если целевой строкой является первый элемент списка. Если вы чаще работаете с последним элементом списка, может быть полезно сохранить prevItemID, а не следующий.
источник
Вы измерили это? Или это только предположение? Не делайте таких предположений без каких-либо доказательств.
Честно говоря, это не "много предметов", для меня это звучит очень мало.
Я предлагаю вам придерживаться подхода «столбец позиции» (если это самая простая реализация для вас). Для таких небольших списков не начинайте ненужную оптимизацию, прежде чем у вас возникнут реальные проблемы с производительностью
источник
Это действительно вопрос масштаба и варианта использования.
Сколько предметов вы ожидаете в списке? Если миллионы, я думаю, что гонг десятичный маршрут является очевидным.
Если 6, то нумерация целых чисел является очевидным выбором. s Также вопросы, как списки или переставить. Если вы используете стрелки вверх и вниз (двигаясь вверх или вниз на один слот за раз), я бы использовал целые числа, а затем поменялся местами с предыдущим (или следующим) на ходу.
Также, как часто вы фиксируете, если пользователь может сделать 250 изменений, а затем зафиксировать сразу, чем я говорю целые числа с перенумерацией снова ...
tl; dr: нужно больше информации.
Редактировать: «Списки желаний» звучит как множество маленьких списков (предположим, это может быть ложно) .. Поэтому я говорю Integer с перенумерацией. (Каждый список содержит свою собственную позицию)
источник
Если целью является минимизация количества операций с базой данных на одну операцию переупорядочения:
При условии, что
Сохраните отсортированный список пожеланий пользователя в виде упакованной последовательности целых чисел (целочисленных массивов) в одном столбце. Каждый раз, когда список пожеланий переупорядочивается, весь массив (одна строка; один столбец) обновляется - что должно быть выполнено с одним обновлением SQL.
https://www.postgresql.org/docs/current/static/arrays.html
Если цель другая, придерживайтесь подхода «столбец позиции».
Относительно «скорости», убедитесь, что сравнительный анализ хранимой процедуры. Хотя выпуск более 20 отдельных обновлений для одного списка пожеланий может быть медленным, возможен быстрый способ использования хранимой процедуры.
источник
Хорошо, я недавно столкнулся с этой сложной проблемой, и все ответы в этом посте вопросов и ответов дали много вдохновения. На мой взгляд, у каждого решения есть свои плюсы и минусы.
Если
position
поле должно быть последовательным без пробелов, то вам нужно будет переупорядочить весь список. Это операция O (N). Преимущество заключается в том, что клиентской стороне не потребуется особая логика для получения заказа.Если мы хотим избежать операции O (N), НО ЕЩЕ сохраняйте точную последовательность, один из подходов состоит в том, чтобы использовать «самоссылку для ссылки на предыдущее (или следующее) значение». Это сценарий со списком связанных учебников. По замыслу, это НЕ повлечет за собой «много других предметов в списке». Однако для этого требуется, чтобы на стороне клиента (веб-служба или, возможно, мобильное приложение) была реализована логика обхода связанного списка для получения порядка.
Некоторые варианты не используют ссылку, то есть связанный список. Они выбирают представление всего порядка в виде отдельного большого двоичного объекта, такого как JSON-array-in-a-string
[5,2,1,3,...]
; такой порядок будет храниться в отдельном месте. Этот подход также имеет побочный эффект, заключающийся в том, что клиентский код должен поддерживать этот отдельный двоичный объект заказа.Во многих случаях нам на самом деле не нужно хранить точный порядок, нам просто нужно поддерживать относительный ранг среди каждой записи. Поэтому мы можем допустить разрыв между последовательными записями. Вариации включают в себя: (1) использование целых чисел с пробелами, такими как 100, 200, 300 ... но вы быстро исчерпаете пробелы и затем потребуется процесс восстановления; (2) использование десятичной дроби, которая идет с естественными пробелами, но вам нужно будет решить, можете ли вы жить с возможным ограничением точности; (3) использование ранга на основе строк, как описано в этом ответе, но будьте осторожны с хитрыми ловушками реализации .
Реальный ответ может быть «это зависит». Пересмотреть ваши требования бизнеса. Например, если это система списков пожеланий, лично я с радостью использовал бы систему, упорядоченную по нескольким разрядам как «обязательные», «обязательные», «возможно, позднее», и затем представлял бы предметы без особого порядок внутри каждого ранга. Если это система доставки, вы можете очень хорошо использовать время доставки в качестве приблизительного значения, которое сопровождается естественным разрывом (и естественным предотвращением конфликтов, так как доставка не будет происходить одновременно). Ваш пробег может варьироваться.
источник
Используйте число с плавающей запятой для столбца позиции.
Затем вы можете изменить порядок списка, изменив только положение столбца в «перемещенной» строке.
В основном, если ваш пользователь хочет позиционировать «красный» после «синего», но до «желтого»
Тогда вам просто нужно рассчитать
После нескольких миллионов перестановок вы можете получить числа с плавающей запятой настолько малые, что между ними не будет никакого «промежуточного» - но это почти так же вероятно, как наблюдение за единорогом.
Вы можете реализовать это, используя целочисленное поле с начальным зазором, скажем, 1000. Таким образом, ваше начальное oredring будет 1000-> синий, 2000-> желтый, 3000-> красный. После «перемещения» красного за синим у вас будет 1000-> синий, 1500-> красный, 2000-> желтый.
Проблема в том, что при кажущемся большом начальном разрыве в 1000 всего 10 ходов вы попадете в ситуацию, такую как 1000-> синий, 1001-puce, 1004-> осада ......, где вы больше не сможете вставить что-нибудь после «синего» без нумерации всего списка. Используя числа с плавающей запятой, всегда будет точка «на полпути» между двумя позициями.
источник
"pos": 1310719, + "pos": 638975.5
. Справедливости ради следует отметить, что большинство людей не создают списки trello с 4 миллионами записей в них, но размер и использование списка Trello довольно распространены для контента, сортируемого пользователем. И все, что можно сортировать по пользователю, не имеет ничего общего с высокой производительностью, скорость сортировки по сравнению с плавающей запятой не имеет значения, особенно учитывая, что базы данных в основном ограничены производительностью ввода-вывода.Хотя ОП кратко затронул идею использования связанного списка для хранения порядка сортировки, он имеет много преимуществ в тех случаях, когда элементы будут часто переупорядочиваться.
Дело в том , что нет ! При использовании связанного списка вставка, удаление и переупорядочение являются
O(1)
операциями, а навязанная базой данных ссылочная целостность гарантирует, что не будет битых ссылок, потерянных записей или циклов.Вот пример:
Обратите внимание на следующее:
FK_Sorting
для предотвращения случайного обращения элементов к неправильному родительскому элементу.UNIQUE INDEX UX_Sorting
Выполняет две роли:NULL
значение, каждый список может иметь только 1 «головной» элемент.SortAfter
значений).Основные преимущества этого подхода:
int
илиreal
, в которых между частями после частого переупорядочения в конечном итоге заканчивается свободное пространство.Этот подход имеет недостатки, однако:
ORDER BY
.VIEW
или TVF, которые используют CTE для добавления производного, содержащего возрастающий порядок сортировки, но это будет дорого использовать в больших операциях.SortAfter
столбец будет ссылаться на элементы, которые не загружены в вашу программу.SELECT * FROM WishlistItems WHERE WishlistId = @wishlistToLoad
).UX_Sorting
она включена, требует поддержки СУБД для отложенных ограничений.NULL
значений в столбце, что, к сожалению, означает, что список может иметь несколько элементов HEAD.State
который представляет собой простой флаг для объявления, является ли элемент списка «активным» или нет - и уникальный индекс игнорирует неактивные элементы.Обходной путь 1: Нужна способность выполнять тривиально
ORDER BY
.Вот ВИД, использующий рекурсивный CTE, который добавляет
SortOrder
столбец:Вы можете использовать этот VIEW в других запросах, где вам нужно отсортировать значения, используя
ORDER BY
:Обходной путь 2: Предотвращение
UNIQUE INDEX
нарушений ограничений при выполнении операций:Добавьте
State
столбец вWishlistItems
таблицу. Столбец помеченHIDDEN
так, что большинство инструментов ORM (например, Entity Framework) не включают его, например, при создании моделей.Операции:
Добавление нового элемента в конец списка:
ItemId
текущий последний элемент в списке, и сохраните его@tailItemId
или используйтеSELECT MAX( SortOrder ) FROM OrderableWishlistItems WHERE WishlistId = @listId
.INSERT INTO WishlistItems ( WishlistId, [Text], SortAfter ) VALUES ( @listId, @text, @tailItemId )
,Изменение порядка позиций 4 ниже 7
Удаление пункта 4 из середины списка:
Если элемент находится в конце списка (т.е. где
NOT EXISTS ( SELECT 1 FROM WishlistItems WHERE SortAfter = @itemId )
), то вы можете сделать одинDELETE
.Если у элемента есть элемент, отсортированный после него, вы выполняете те же шаги, что и при переупорядочении элемента, за исключением того,
DELETE
что вы выполняете его впоследствии, вместо настройкиState = 1;
.источник