Представлять порядок в реляционной базе данных - PullRequest
33 голосов
/ 22 августа 2008

У меня есть коллекция объектов в базе данных. Изображения в фотогалерее, товары в каталоге, главы в книге и т. Д. Каждый объект представлен в виде строки. Я хочу иметь возможность произвольно упорядочивать эти изображения, сохраняя этот порядок в базе данных, чтобы при отображении объектов они были в правильном порядке.

Например, допустим, я пишу книгу, и каждая глава - это объект. Я пишу свою книгу и располагаю главы в следующем порядке:

Введение, Доступность, Форма против Функции, Ошибки, Согласованность, Заключение, Индекс

Идет к редактору и возвращается в следующем предложенном порядке:

Введение, форма, функция, доступность, согласованность, ошибки, заключение, индекс

Как я могу сохранить этот порядок в базе данных надежным и эффективным способом?

У меня были следующие идеи, но я не в восторге ни от одной из них:

  1. Массив. Каждая строка имеет идентификатор заказа, при изменении заказа (путем удаления с последующей вставкой) идентификаторы заказа обновляются. Это делает поиск легким, так как это всего лишь ORDER BY, но его легко сломать.

    // REMOVAL
    UPDATE ... SET orderingID=NULL WHERE orderingID=removedID
    UPDATE ... SET orderingID=orderingID-1 WHERE orderingID > removedID
    // INSERTION
    UPDATE ... SET orderingID=orderingID+1 WHERE orderingID > insertionID
    UPDATE ... SET orderID=insertionID WHERE ID=addedID

  2. Связанный список. В каждой строке есть столбец для идентификатора следующей строки в порядке. Обход здесь кажется дорогостоящим, хотя может каким-то образом использовать ORDER BY, о котором я не думаю.

  3. Разнесенный массив. Установите orderingID (как в # 1), чтобы он был большим, поэтому первый объект равен 100, второй - 200 и т. Д. Затем, когда происходит вставка, вы просто помещаете его в (objectBefore + objectAfter)/2. Конечно, иногда это необходимо перебалансировать, чтобы у вас не было слишком близко друг к другу вещей (даже с плавающей точкой вы в конечном итоге столкнетесь с ошибками округления).

Ничто из этого не кажется мне особенно элегантным. У кого-нибудь есть лучший способ сделать это?

Ответы [ 11 ]

6 голосов
/ 22 августа 2008

Другой альтернативой будет (если ваша СУБД поддерживает это) использование столбцов типа array. Хотя это нарушает правила нормализации, это может быть полезно в подобных ситуациях. Одна база данных, о которой я знаю, имеет массивы - это PostgreSQL.

4 голосов
/ 22 августа 2008

Миксы act_as_list в Rails обрабатывают это в основном так, как вы описали в # 1. Он ищет столбец INTEGER с именем position (из которого вы можете переопределить name) и использует его для выполнения ORDER BY. Когда вы хотите изменить порядок вещей, вы обновляете позиции. Он прекрасно мне служил каждый раз, когда я его использовал.

В качестве примечания, вы можете избавиться от необходимости всегда делать повторное позиционирование на ВСТАВКАХ / УДАЛЕНИЯХ, используя разреженную нумерацию - что-то вроде базовых значений в тот день ... вы можете нумеровать свои позиции 10, 20, 30 и т. д., и если вам нужно вставить что-то между 10 и 20, вы просто вставляете это с позицией 15. Аналогично, при удалении вы можете просто удалить строку и оставить пробел. Вам необходимо выполнять повторную нумерацию только тогда, когда вы действительно меняете порядок или если вы пытаетесь выполнить вставку, и в ней нет подходящего пробела для вставки.

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

3 голосов
/ 25 августа 2008

Просто мысль, учитывая опция # 1 против # 3 : разве опция с разделенным массивом (# 3) не только откладывает проблему нормального массива (# 1)? Какой бы алгоритм вы ни выбрали, либо он сломан, и вы столкнетесь с проблемами с № 3 позже, либо он сработает, и тогда № 1 также будет работать.

2 голосов
/ 18 сентября 2008

Используйте число с плавающей запятой для представления позиции каждого элемента:

Элемент 1 -> 0,0

Элемент 2 -> 1,0

Элемент 3 -> 2,0

Элемент 4 -> 3,0

Вы можете поместить любой предмет между любыми двумя другими предметами простым делением пополам:

Элемент 1 -> 0,0

Элемент 4 -> 0,5

Элемент 2 -> 1,0

Элемент 3 -> 2,0

(перемещен элемент 4 между пунктами 1 и 2).

Процесс деления пополам может продолжаться почти бесконечно из-за способа кодирования чисел с плавающей запятой в компьютерной системе.

Элемент 4 -> 0,5

Элемент 1 -> 0,75

Элемент 2 -> 1,0

Позиция 3 -> 2.0

(переместить элемент 1 в позицию сразу после пункта 4)

2 голосов
/ 22 августа 2008

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

Кроме того, я бы предположил, что ORDER BY будет довольно сильно оптимизирован поставщиками баз данных, поэтому использование этой функции будет выгодно для производительности, а не для реализации связанного списка.

2 голосов
/ 22 августа 2008

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

1 голос
/ 29 марта 2009

Так как я в основном сталкивался с этим с Django, я нашел это решение наиболее подходящим. Кажется, что нет никакого «правильного» способа сделать это в реляционной базе данных.

1 голос
/ 18 сентября 2008

У меня тоже была эта проблема. Я был под большим давлением времени (не так ли все), и я выбрал вариант № 1, и только обновленные строки изменились.

Если вы поменяете позицию 1 на позицию 10, просто сделайте два обновления, чтобы обновить номера заказов для позиции 1 и позиции 10. Я знаю, что это алгоритмически просто, и это O (n) худший случай, но этот худший случай когда у вас есть полная перестановка списка. Как часто это будет происходить? Это тебе ответить.

1 голос
/ 22 августа 2008

Я бы сделал последовательный номер с триггером на столе, который «освобождает место» для приоритета, если он уже существует.

0 голосов
/ 15 декабря 2018

Схема № 1 и Схема № 3 имеют одинаковую сложность в каждой операции, за исключением записи INSERT. Схема # 1 имеет O (n) пишет на INSERT, а схема # 3 имеет O (1) пишет на INSERT.

Для любой другой операции с базой данных сложность одинакова.

Схема № 2 даже не должна рассматриваться, потому что ее DELETE требует O (n) чтения и записи. Схемы № 1 и Схема № 3 имеют O (1) DELETE как для чтения, так и для записи.

Новый метод

Если у ваших элементов есть отдельный родительский элемент (т.е. они имеют общую строку внешнего ключа), тогда вы можете попробовать следующее ...

Django предлагает независимое от базы данных решение для хранения списков целых чисел в CharField(). Один недостаток заключается в том, что максимальная длина хранимой строки не может превышать max_length, что зависит от DB.

С точки зрения сложности, это дало бы Схему # 1 O (1) записи для INSERT, потому что информация об упорядочении будет храниться как одно поле в строке родительского элемента.

Другим недостатком является то, что JOIN для родительской строки теперь требуется для обновления порядка.

https://docs.djangoproject.com/en/dev/ref/validators/#django.core.validators.validate_comma_separated_integer_list

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...