Как связать две строки в одной таблице

11

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

  1. Храните отношения и их обратное
  2. Сохраняйте связь одним способом, ограничивайте базу данных от ее хранения другим способом и имейте два индекса с противоположными порядками для FK (один индекс является индексом PK)
  3. Сохраните отношения в одном направлении с двумя индексами и позвольте второму быть вставленным так или иначе (звучит немного противно, но эй, полнота)
  4. Создайте некоторую таблицу группировки и поместите в нее FK на исходной таблице. (Возникает много вопросов. Таблица группировки будет иметь только номер; зачем даже таблица? Сделать FK NULLable или иметь группы с одной связанной строкой?)

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

Вот SQLFiddle для игры: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Получается, что это PostgreSQL, так как это то, что я использую, но я не думаю, что этот вопрос очень специфичен для PostgreSQL.) В настоящее время он хранит как отношение, так и обратное, просто в качестве примера.

jpmc26
источник
Может ли данное значение быть связано с несколькими другими? Всегда ли данное значение связано с другим? Они разделяют те же самые другие общие данные?
Philᵀᴹ
Да, они могут быть связаны с более чем 1 другим рядом. Нет, они не всегда связаны с другим рядом. Они не обязательно имеют какие-либо общие данные. Спасибо.
jpmc26
К сожалению. Забыл @Phil. Также отредактировано, чтобы добавить потенциальную структуру, которая только что пришла мне в голову.
jpmc26

Ответы:

9

То, что вы разработали, хорошо. Что нужно добавить, так это ограничение, чтобы сделать отношения бесцельными. Таким образом, у вас не может быть (1,5)строки без добавления (5,1)строки.

Это может быть выполнено * с помощью самообращающегося ограничения на таблицу мостов.

*: это может быть выполнено в Postgres, Oracle, DB2 и всех СУБД, в которых реализованы ограничения внешнего ключа, как описано в стандарте SQL (отложено, например, проверено в конце транзакции.) Отложенная проверка на самом деле не нужна, так как в SQL- Сервер, который проверяет их в конце оператора и эта конструкция все еще работает. Вы не можете сделать это в MySQL, потому что «InnoDB проверяет ограничения UNIQUE и FOREIGN KEY строка за строкой» .

Итак, в Postgres следующее будет соответствовать вашим требованиям:

CREATE TABLE x
(
  x_id SERIAL NOT NULL PRIMARY KEY,
  data VARCHAR(10) NOT NULL
);

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    FOREIGN KEY (x_id2, x_id1)
    REFERENCES bridge_x (x_id1, x_id2)
);

Проверено на: SQL-Fiddle

Если вы попытаетесь добавить строку (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Это терпит неудачу с:

ОШИБКА: вставка или обновление таблицы "bridge_x" нарушает ограничение внешнего ключа "x_x_directionless".
Подробно: Ключ (x_id2, x_id1) = (5, 1) отсутствует в таблице "bridge_x" .:
INSERT INTO bridge_x VALUES (1,5)

Кроме того, вы можете добавить CHECKограничение, если хотите запретить (y,y)строки:

ALTER TABLE bridge_x
  ADD CONSTRAINT x_x_self_referencing_items_not_allowed
    CHECK (x_id1 <> x_id2) ;

Как вы упомянули, есть и другие способы реализовать это, например, хранить только одно направление отношения (в одной строке, а не в двух) путем принудительного ввода более низкого идентификатора в x_id1и более высокого идентификатора в x_id2столбце. Это выглядит проще для реализации, но обычно приводит к более сложным запросам позже:

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    CHECK (x_id1 <= x_id2)                       -- or "<" to forbid `(y,y)` rows
);
ypercubeᵀᴹ
источник