Ответ 1
Операторы 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
. Подробности: -
Прочитайте главу о указателях в руководстве.