Лучшее представление упорядоченного списка в базе данных?

Я знаю, что это противоречит принципам реляционной базы данных, но позвольте мне описать ситуацию.

У меня есть страница, на которой пользователь разместит несколько элементов.

 ________________
| -Item1         |
| -Item2         |
| -Item3         |
| -Item4         |
|________________|

Эти предметы должны оставаться в том порядке, который пользователь дает им. Однако этот заказ может быть изменен произвольным числом раз пользователем.

 ________________
| -Item1         |
| -Item4         |
| -Item2         |
| -Item3         |
|________________|

Подход 1

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

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | index
                  | content 

С помощью этого решения вы можете выбрать элементы where pid = Page.pid и order by index, что удобно. Однако каждый раз, когда вы меняете заказ, вы должны меняться где угодно между одним другим пунктом (наилучшим случаем) и всеми другими элементами (наихудший случай).

Подход 2

Я также подумал о создании "связанного списка", например структуры данных, где каждый элемент указывает на следующий элемент в списке.

Page           Item
-----------    ---------------
FK | pid       FK | pid 
   | name      PK | iid 
                  | next
                  | content 

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

Есть ли подход, о котором я не думал? Пожалуйста, дайте мне знать.

Ответ 1

Я думаю, что @a1ex07 находится на правильном пути здесь (+1). Я не думаю, что пробелы в itemOrder нарушают 3NF, но я беспокоюсь о другом нарушении 3NF (подробнее об этом ниже). Мы также должны следить за плохими данными в поле itemOrder. Вот как бы я начал:

create table pages (
  pid int,
  primary key (pid)
);

create table users (
  uid int,
  primary key (uid)
);

create table items (
  iid int,
  primary key (iid)
);

create table details (
  pid int not null references pages(pid),
  uid int not null references users(uid),
  iid int not null references items(iid), 
  itemOrder int,
  primary key (pid, uid, iid),
  unique (pid, uid, itemOrder)
);

Первичный ключ гарантирует, что для каждой страницы для каждого пользователя есть уникальные элементы. Уникальное ограничение гарантирует, что для каждой страницы для каждого пользователя есть уникальные элементы itemOrders. Здесь мое беспокойство о 3NF: в этом случае itemOrder не полностью зависит от первичного ключа; это зависит только от частей (pid, uid). Это даже не 2NF; и что проблема. Мы могли бы включить itemOrder в первичный ключ, но потом я беспокоюсь, что он может быть не минимальным, поскольку ПК должны быть. Возможно, нам придется разложить это на несколько таблиц. Еще думаю.,.


[РЕДАКТИРОВАТЬ - Больше размышлений на эту тему., ]

Предположения

  • Есть пользователи.

  • Есть страницы.

  • Есть элементы.

  • (страница, пользователь) идентифицирует SET элементов.

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

  • Мы не хотим дублировать элементы в списке (странице, пользователе).

План A

Убейте таблицу details выше.

Добавьте таблицу ItemsByPageAndUser, чтобы представить SET элементов, идентифицированных (страница, пользователь).

create table ItemsByPageAndUser (
   pid int not null references pages(pid),
   uid int not null references users(uid),
   iid int not null references items(iid),
  primary key (pid, uid, iid)   
)

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

create table SlotsByPageAndUser (
   pid       int not null references pages(pid),
   uid       int not null references users(uid),
   slotNum   int not null,
   iidInSlot int          references items(iid),
 primary key (pid, uid, slotNum),   
 foreign key (pid, uid, iid) references ItemsByPageAndUser(pid, uid, iid),
 unique (pid, uid, iid)
)

Примечание 1: iidInSlot имеет значение NULL, поэтому мы можем иметь пустые слоты, если хотим. Но если есть элемент, он должен быть проверен против таблицы элементов.

Примечание 2. Нам нужен последний FK, чтобы мы не добавляли элементы, которые не входят в набор возможных элементов для этого (пользователь, страница).

Примечание 3. Уникальное ограничение на (pid, uid, iid) обеспечивает нашу цель разработки уникальных элементов в списке (предположение 6). Без этого мы могли бы добавить столько элементов из набора, которое идентифицируется (страница, пользователь), как нам нравится, если они находятся в разных слотах.

Теперь мы прекрасно отделяем элементы от своих слотов, сохраняя их общую зависимость от (страницы, пользователя).

Эта конструкция, безусловно, находится в 3NF и может быть в BCNF, хотя я беспокоюсь о SlotsByPageAndUser в этом отношении.

Проблема состоит в том, что из-за единственного ограничения в таблице SlotsByPageAndUser мощность отношения между SlotsByPageAndUser и ItemsByPageAndUser взаимно однозначна. В общем случае отношения 1-1, которые не являются подтипами сущностей, являются неправильными. Конечно, есть исключения, и, возможно, это одно. Но, может быть, есть еще лучший способ.,.

План B

  • Убейте таблицу SlotsByPageAndUser.

  • Добавьте столбец slotNum в ItemsByPageAndUser.

  • Добавьте уникальное ограничение на (pid, uid, iid) до ItemsByPageAndUser.

Теперь это:

create table ItemsByPageAndUser (
   pid     int not null references pages(pid),
   uid     int not null references users(uid),
   iid     int not null references items(iid),
   slotNum int,
 primary key (pid, uid, iid),   
 unique (pid, uid, slotNum)
)

Примечание 4. Оставляя slotNum значение nullable, сохраняет нашу способность указывать элементы в наборе, которых нет в списке. Но.,.

Примечание 5. Помещение уникального ограничения на выражение, включающее столбец с нулевым значением, может привести к "интересным" результатам в некоторых базах данных. Я думаю, что это будет работать, как мы планируем в Postgres. (См. это обсуждение здесь, на SO.) Для других баз данных ваш пробег может отличаться.

Теперь нет беспорядочных отношений 1-1, которые вокруг, так что лучше. Это все еще 3NF, поскольку единственный неключевой атрибут (slotNum) зависит от ключа, всего ключа и всего лишь от ключа. (Вы не можете спросить о slotNum, не сообщая мне, какую страницу, пользователя и элемент вы говорите.)

Это не BCNF, потому что [ (pid, uid, iid)slotNum] и [(pid,uid,slotNum)iid]. Но поэтому у нас есть уникальное ограничение на (pid, uid, slotNum), которое предотвращает попадание данных в несогласованное состояние.

Я думаю, что это приемлемое решение.

Ответ 2

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

Ответ 3

Используйте Подход 1 и поддерживайте последствия обновления индекса. Если вы не имеете дело с миллионами элементов на странице, вы вряд ли найдете недостающую производительность, и вы сохраните всю мощь SQL в работе с наборами данных.

В дополнение к тому, что с чистого не-процедурного SQL будет работать сложнее, Подход 2 по-прежнему потребует, чтобы вы прошли через список, чтобы найти нужное место для повторного подключения "ссылок", когда переупорядочивая предмет.

Ответ 4

Вы можете добавить столбец нового символа (nvarchar) в таблицу Page с именем order, которая содержит список с разделителями iid в том порядке, который вы предпочитаете, т.е. 1,4,3,2. Преимущество - это просто одно поле в одной таблице для поддержки - очевидным недостатком будет необходимость написать служебную функцию для преобразования между символьным и числовым типами, которые на самом деле, вероятно, не занимают слишком много времени.

Ответ 5

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