Как сделать способ заказывать записи в БД?

Мне нужно сделать возможность переупорядочивать записи, хранить
в БД (я использую MS SQL, но, похоже, это неважно). Я вижу
возможные 2 решения:

1. Добавить колонку заказа. Затем, если мы хотим переупорядочить 2
записи, нам нужно изменить значения столбца Order для этих 2
записей. Значение первой записи установлено на вторую и наоборот.
Проблема - мы должны добавить уникальное ограничение для
упорядочения столбца, и мы не можем сделать это легко, потому что
на первом этапе мы ломаем ошибку, например:

> 
> {"Нарушение ограничения UNIQUE KEY" IX\_EscortItems ". Невозможно
> вставить   дублировать ключ в объекте 'dbo.EscortItems'.
> Значение повторяющегося ключа   (2, 20). \ R \
> nПриложение завершено. "}
> 
> 
> 

2. Второй подход - это отдельный список. Добавьте столбец
«Родитель», сохраните нуль для первого элемента или идентификатора
предыдущего элемента. Но у нас та же проблема с единственным
ограничением.

Каков правильный подход к решению этой проблемы?

Существует несколько подходов, которые вы можете
использовать:

1. The easiest one is to forgo the `UNIQUE` constraint
on the column that specifies the ordering (either as linked list or
as sequence number). It is then up to the software to ensure that
eventually the database is consistent again.
2. Use sequence numbers with gaps.  

If you initially start with sequence numbers like 10, 20, 30, etc.,
then you can re-order them by allocating sequence numbers
in-between the ones already given out.

For example, to put the fourth element between the first and
second, the sequence numbers would become 10, 15, 20, 30, 50,
etc.

To avoid running out of gaps to place elements, you can either
periodically normalize the sequence numbers, or you can use
floating point sequence numbers.



Четыре варианта для вас. Мне нравится вариант 4, но первый из
них проще всего.

Вариант 1. Просто запустите один выше
-------------------------------------

Значения в столбце `Order` не имеют значения, только
их относительные значения. Будете ли вы указывать десять строк от 1
до 10, от 11 до 20 или от 57 до 66 - все одинаковы.

Таким образом, очень простое решение состоит в том, чтобы
запустить один из них выше, чем ваш самый высокий
`Order` : если ваши строки имеют значения
`Order` от 1 до 10, и вам нужно их отменить, присвойте
им значения от 20 до 11.

Они закончат сортировку правильно, и у вас не будет никаких
нарушений уникальности, потому что они находятся в отдельном
диапазоне значений.

С другой стороны, это немного сложно, если вы не хотите повторно
делать весь список и просто хотите поменять два значения.

Вариант 2. Включить номер версии сортировки в ограничение
уникальности
----------------------------------------------------------------------

Вместо добавления одного столбца `Order` добавьте
два, `SortOrder` и `SortVersion` . Примените
ограничение уникальности к комбинации этих двух столбцов.

Если вы хотите переупорядочить элементы в списке, увеличьте
столбец `SortVersion` на единицу, когда вы сохраните
новое значение для `SortVersion` .

Вариант 3. Удалите строки и снова вставьте их
---------------------------------------------

В зависимости от используемой структуры это может быть самый
простой вариант, так как у вас будут записи, хранящиеся в уровне
приложения как объекты домена. Просто вытрите их и вставьте их в
желаемом порядке.

Это может оказаться невозможным, если вы не используете EF или
если есть отношения FK, которые приведут к каскадному удалению.

Вариант 4. Сохраните столбец «упорядочить по» в отдельной
таблице
-----------------------------------------------------------------

Технически это самый нормализованный вариант, поскольку порядок
сортировки, вероятно, не является атрибутом объекта, а является
атрибутом отношения между сущностью и другим объектом. Таким
образом, `Order` фактически нарушает 3NF, верьте или нет
- значения столбцов должны зависеть от ключа (1NF), всего ключа
(2NF) и *ничего, кроме* первичного ключа (3NF), и в этом
случае столбец `Order` зависит от отношения объекта в
более широком контексте. Чтобы нормализовать это, вы должны иметь
таблицу соединений.

Итак, допустим, у вас есть таблица счетов и таблица line\_item.
Добавьте таблицу третьей таблицы, invoice\_line\_item, которая
содержит `InvoiceID` , `LineItemID` и
`SortOrder` . Если вы хотите изменить порядок строк,
удалите их все, а затем снова вставьте их. Это позволяет избежать
любых проблем с FK или связанными объектами, поскольку эти FK не
будут в этой третьей таблице, а одна из других таблиц. Это сводится
к изменению отношения между позициями и счетами, поэтому имеет
смысл удалить и заменить их. Естественно, вы захотите обернуть все
это в транзакцию.



Единственным ограничением должно быть сочетание столбца Order
*и* внешнего ключа с родительской таблицей. В качестве
примера я использую «корзину покупок».

```
Table: ShoppingCartItems
- ShoppingCartId (FK to ShoppingCarts table)
- DisplayNumber (int)

```

В этом случае единственное ограничение обеспечит комбинацию
`ShoppingCartItems.ShoppingCartId` и
`ShoppingCartItems.DisplayNumber` уникально в таблице -
так, чтобы в корзине покупок не было более одного элемента с тем же
порядком отображения.



Я использую отрицательный для temp

```
declare int @rowA = 7 
declare int @rowB = 22

update table set order = [[email protected]](/cdn-cgi/l/email-protection) where order =  @rowA; 
update table set order =  @rowA where order =  @rowB; 
update table set order =  @rowB where order = [[email protected]](/cdn-cgi/l/email-protection);

```

Вы можете обернуть его в транзакцию, но, скорее всего, вы не
столкнетесь с конфликтами



Часто при использовании столбца заказа важно, чтобы сам заказ, а
не конкретные значения. Поэтому, если вы используете столбец
заказов, вам не нужно заботиться о том, какие числа в нем есть,
если они отсортированы правильно относительно друг друга.

Поэтому, если вы хотите переместить запись в порядке, вы
можете:

* увеличивать все значения порядка из точки вставки и выше для
создания открытого слота
* обновить значение порядка записи в этом слоте.

Нет конфликтов

Далее проиллюстрировано. Если у вас есть значения для
заказа:

```
1,2,3,4,5,6,7,8

```

и вы хотите переместить элемент на # 7 в # 3, вы сначала
запускаете обновление, например

```
update foo set order = order +1 where container = X and order >=3

```

Это дает вам

```
1,2,4,5,6,7,8,9

```

Затем вы устанавливаете значение заказа для определенного
элемента, который хотите переместить

```
update foo set order = 3 where id = 102334

```

И вы можете

```
1,2,3,4,5,6,7,9

```

Вы должны следовать аналогичной схеме для других типов
переустановки.

Это, очевидно, лучше всего работает, когда вы имеете дело с
маленькими или умеренными размерами. Если у вас есть 15 000 000
записей с определенным порядком, вы, вероятно, захотите
использовать один из других подходов.



Здесь вы должны углубиться в требования и не слишком
беспокоиться о реализации. Он заботится о себе. Нет лучшего способа
сделать это.

Кто-нибудь действительно заботится, есть ли у вас уникальные
номера для сортировки? Что делать, если пользователь (при условии,
что я делаю это вручную), мне все равно, все ли они равны нулю?
Если вы думаете, что они непреднамеренно будут включать в себя
дубликат, и они предполагают, что заказ будет соответствовать тому,
что они видят в своем интерфейсе, вы всегда можете опубликовать
предупреждение. Опять же, существует ли потребность в этом
уникальном и отсортированном номере, например, в системе
выставления счетов (я помню, как работала над системой для фирмы в
Германии, которая не допускает пробелов в этих числах)?

Если для сортировки есть какой-то чрезвычайно сложный и
трудоемкий алгоритм, и вы хотите сохранить это значение для целей
производительности, зачем беспокоиться о любом ограничении в db?
Пусть код обрабатывает его.

Я работаю с системой, которая позволяет пользователю настраивать
сортировку по выпадающим спискам. Администратор назначает номера, и
вы просто заставляете его работать. Ловушка для дубликатов просто
не стоит.



Часть основной идеи реляционных баз данных в целом состоит в
том, что записи не имеют «неотъемлемого» порядка в самой базе
данных 1 .

Заказ обычно накладывается на результат запроса. В этом случае
мы можем решить, в каком столбце (столбцах) указывается порядок, в
котором мы хотим отображать элементы, и соответственно заказывать
результат.

Например, мы могли бы иметь возможность создавать отчеты о
заказах по дате/времени каждого заказа (т. Е. Отображать их в том
порядке, в котором они были размещены):

```
select ID, value, date /* , ... */ from Orders
order by ship_date

```

... или дата/время отправки заказа,

```
select ID, value, date /* , ... */ from Orders
order by ship_date

```

... или, возможно, порядок убывания по размеру (так что мы можем
быстро увидеть самые большие заказы, независимо от того, когда они
произошли).

```
select ID, value, date /* , ... */ from Orders
order by value desc

```

Вы можете, конечно, добавить столбец `order_by` (по
другому имени, если хотите), когда/если вы действительно хотите,
чтобы заказы отображались в порядке, который является полностью
произвольным (или, по крайней мере, на основе критериев, которые не
хранятся в базе данных). В этом случае, когда вы хотите изменить
порядок, у вас есть несколько вариантов. Один заключается в том,
чтобы просто не сообщить базе данных, что этот столбец содержит
уникальные значения. Другой - просто избегать дублирования. Для
вашего примера вы начали с записей с номерами 1 и 2. Чтобы отменить
их, вы можете изменить числа на 4 и 3 соответственно.

Чтобы выполнить это программно, вы найдете наибольшее значение в
настоящее время в столбце 2 , а затем используйте одно
большее, чем базовое значение при переупорядочивании. Вы также
можете проверить минимальное значение, которое в настоящее время
используется. Если это больше, чем количество записей, вы можете
повторно начать нумерацию с 0.

---

1. Хотя MS SQL имеет концепцию «кластерного индекса»,
которая напрямую связана с порядком хранения записей - это в
основном оптимизация. 2. Обычно вы индексируете этот столбец,
поэтому эту операцию мы можем ожидать, что эта операция будет
довольно быстрой.



Три предложения:

1. Всегда делайте свои обновления в последовательности, которая не
вызывает дубликатов. Например, чтобы поменять местами 2 и 3,
сначала обновить 2 с нулевым значением, затем вставить 2 для замены
3, а затем вставить 3.
2. Вероятно, вам понадобится запрос для вставки записи в середине
отсортированного списка. Если вы последовательно записываете свои
записи, вам нужно будет перенумеровать многие записи, чтобы
вставить их. Если вы используете связанный список, вам нужно
обновить только два. По этой причине, я надеюсь, вы найдете
связанный список менее дорогостоящим во время выполнения и,
вероятно, проще закодировать.
3. Я считаю условным назвать ваш столбец `sort_key` ,
`порядковый` или, возможно, `position` .

Внедрение с открытым исходным кодом для Active Record в [ruby](/tag1546) может вдохновить вас. Прочтите источник
[act\_as\_list gem](https://github.com/swanandp/acts_as_list/blob/master/lib/acts_as_list/active_record/acts/list.rb#L37) и сравнить его с
этими идеями.