Как реализовать отношения "многие ко многим" в PostgreSQL?
33 Radu Gheorghiu [2012-03-20 18:28:00]
Я считаю, что титул не требует объяснений. Как создать структуру таблиц в PostgreSQL для создания отношения "многие ко многим".
Мой пример:
Product(name, price);
Bill(name, date, Products);
database postgresql database-design many-to-many
1 ответ
156 Решение Erwin Brandstetter [2012-03-20 18:52:00]
Операторы DDL могут выглядеть следующим образом:
CREATE TABLE product (
product_id serial PRIMARY KEY -- implicit primary key constraint
, product text NOT NULL
, price numeric NOT NULL DEFAULT 0
);
CREATE TABLE bill (
bill_id serial PRIMARY KEY
, bill text NOT NULL
, billdate date NOT NULL DEFAULT CURRENT_DATE
);
CREATE TABLE bill_product (
bill_id int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id) -- explicit pk
);
Я сделал несколько настроек:
-
Связь n: m обычно выполняется отдельной таблицей -
bill_productв этом случае. -
Я добавил столбцы
serialв качестве суррогатных первичных ключей. Я очень рекомендую это, потому что название продукта вряд ли уникально. Кроме того, для обеспечения уникальности и ссылки на столбец во внешних ключах гораздо дешевле с 4-байтовымinteger, чем с строкой, хранящейся какtextилиvarchar.
В Postgres 10 или более поздней версии вместоIDENTITY. Подробности: -
Не используйте имена основных типов данных, например
date, как идентификаторы. Хотя это возможно, это плохой стиль и приводит к запутыванию ошибок и сообщений об ошибках. Используйте юридические, строчные, некотируемые идентификаторы. Никогда не используйте зарезервированные слова и избегайте двойных кодовых идентификаторов случая, если вы можете. -
nameне является хорошим именем. Я переименовал столбецnameтаблицыproductкакproduct. Это лучше соглашение об именах. В противном случае, когда вы присоединитесь к нескольким таблицам в запросе, который вы делаете много в реляционной базе данных, вы получаете несколько столбцов с именемnameи должны использовать псевдонимы столбцов, чтобы разобраться в беспорядке. Это не полезно. Другой распространенный анти-шаблон будет простоidв качестве имени столбца.
Я не уверен, каким будет имяbill. Может быть,bill_idможет быть именем в этом случае. -
priceимеет типnumericдля хранения дробных чисел точно как введенный (произвольный тип точности вместо типа с плавающей точкой). Если вы имеете дело исключительно с целыми числами, сделайте этоinteger. Например, вы можете сэкономить цены как центы. -
amount("Products"в вашем вопросе) входит в таблицу компоновкиbill_productи имеет типnumeric. Опять же,integer, если вы имеете дело исключительно с целыми числами. -
Вы видите внешние ключи в
bill_product? Я создал как каскадные изменения (ON UPDATE CASCADE): Если aproduct_idилиbill_idдолжны измениться, это изменение будет каскадно для всех зависимых записей вbill_product, и ничего не сломается.
Я также использовалON DELETE CASCADEдляbill_id: если вы удаляете счет, детали будут удалены вместе с ним.
Не для продуктов: вы не хотите удалять продукт, который использовался в счете. Postgres выдаст ошибку, если вы попытаетесь это сделать. Вы добавили бы еще один столбец вproductдля обозначения устаревших строк. -
Все столбцы в этом базовом примере заканчиваются на
NOT NULL, поэтому значенияNULLнедопустимы. (Да, все столбцы - столбцы, используемые в первичном ключе, определяютсяUNIQUE NOT NULLавтоматически.) Это потому, что значенияNULLне имеют смысла ни в одном из столбцов. Это облегчает жизнь начинающим. Но вы не сможете так легко уйти, вам все равно нужно пониматьNULLобработку. Дополнительные столбцы могут позволить значенияNULL, функции и объединения могут вводить значенияNULLв запросах и т.д. -
Прочитайте главу
CREATE TABLEв руководстве. -
Первичные ключи реализуются с уникальным индексом в ключевых столбцах, что делает запросы с условиями на столбцах PK быстрыми. Однако последовательность ключевых столбцов имеет значение в многоколоночных ключах. Поскольку PK на
bill_productнаходится на(bill_id, product_id)в моем примере, вы можете захотеть добавить еще один индекс только дляproduct_idили(product_id, bill_id), если у вас есть запросы, которые ищут aproduct_idи nobill_id. Подробности: -
Прочитайте главу о указателях в руководстве.