Як реалізувати відносини багато-до-багатьох у PostgreSQL?


101

Я вважаю, що назва сама собою пояснюється. Як створити структуру таблиці в PostgreSQL для створення взаємозв'язку "багато-до-багатьох".

Мій приклад:

Product(name, price);
Bill(name, date, Products);

2
видаліть товари зі таблиці рахунків, створіть нову таблицю під назвою "bill_products" із двома полями: одне вказує на товари, одне вказує на рахунок. зробити ці два поля первинним ключем нової таблиці.
Marc B

Так bill_products (рахунок, продукція); ? І обидва вони ПК?
Раду Георгіу

1
так вони були б індивідуально ФК, вказуючи на свої столи, і разом вони були б ПК для нової таблиці.
Marc B

Отже, bill_product (посилання на товар product.name, посилання на рахунок bill.name, (товар, рахунок) первинний ключ)?
Раду Георгіу

Вони вказали б, які б поля PK були в таблицях Product і Bill.
Marc B

Відповіді:


305

Оператори SQL 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стовпці як сурогатні первинні ключі . У Postgres 10 або пізнішої версії розгляньте натомість IDENTITYстовпець . Подивитися:

    Я настійно рекомендую це, оскільки назва продукту навряд чи є унікальною (не є хорошим "природним ключем"). Крім того, дотримання унікальності та посилання на стовпець у зовнішніх ключах зазвичай дешевше з 4-байтовим integer(або навіть 8-байтовим bigint), ніж із рядком, що зберігається якtext або varchar.

  • Не використовуйте імена основних типів даних, dateяк ідентифікатори . Хоча це можливо, це поганий стиль і веде до плутанини помилок та повідомлень про помилки. Використовуйте юридичні, малі регістри, ідентифікатори без котирувань . Ніколи не використовуйте зарезервовані слова та уникайте подвійних лапок із змішаними ідентифікаторами, якщо можете.

  • "ім'я" не є добрим ім'ям. Я перейменував стовпець таблиці productна product( product_nameабо подібний). Це краща угода про іменування . В іншому випадку, коли ви приєднуєтесь до кількох таблиць у запиті - чого ви багато робите в реляційній базі даних - у вас виходить декілька стовпців з іменем "name" і вам доведеться використовувати псевдоніми стовпців, щоб розібратися в хаосі. Це не корисно. Іншим широко розповсюдженим анти-шаблоном буде просто "id" як назва стовпця.
    Я не впевнений, як би називали a bill. bill_idцього, мабуть, буде достатньо.

  • priceмає тип даних, numeric щоб зберігати дробові числа точно так, як їх було введено (довільний тип точності замість типу з плаваючою комою). Якщо ви маєте справу виключно з цілими числами, зробіть це integer. Наприклад, ви можете заощадити ціни як центи .

  • amount( "Products"В вашому запитанні) переходить в сполучній таблиці bill_productі типу , numericа також. Знову ж таки, integerякщо ви маєте справу виключно з цілими числами.

  • Ви бачите зовнішні ключі в bill_product? Я створив і зміни каскадних: ON UPDATE CASCADE. Якщо a product_idабо bill_idмає змінитися, зміна каскадується до всіх залежних записів, bill_productі нічого не ламається. Це лише посилання без власного значення.
    Я також використовував ON DELETE CASCADEдля bill_id: Якщо рахунок видаляється, його реквізити помирають разом з ним.
    Для продуктів не так: Ви не хочете видаляти товар, який використовується в рахунку. Postgres видасть помилку, якщо ви спробуєте це. Ви б додали ще один стовпець для productпозначення застарілих рядків ("soft-delete").

  • Всі стовпці в цьому базовому прикладі виявляються такими NOT NULL, тому NULLзначення не допускаються. (Так, усі стовпці - стовпці первинного ключа визначаються UNIQUE NOT NULLавтоматично.) Це тому, що NULLзначення не мали б сенсу в жодному із стовпців. Це полегшує життя новачка. Але ви не втечете так легко, вам все одно потрібно зрозуміти NULLповодження . Додаткові стовпці можуть дозволяти NULLзначення, функції та об’єднання, можуть вводити NULLзначення у запити тощо.

  • Прочитайте розділ CREATE TABLEу посібнику .

  • Первинні ключі реалізовані з унікальним індексом на стовпцях ключів, що робить запити з умовами в стовпцях PK швидкими. Однак послідовність стовпців ключів є релевантною в багатостолбкових ключах. Оскільки у моєму прикладі PK on bill_productувімкнено (bill_id, product_id), можливо, ви захочете додати інший індекс лише для product_idабо (product_id, bill_id)якщо у вас є запити, які шукають дане product_idі ні bill_id. Подивитися:

  • Прочитайте розділ про покажчики в посібнику .


Як я можу створити індекс для таблиці відображення bill_product ? Зазвичай це має виглядати так: CREATE INDEX idx_bill_product_id ON booked_rates(bill_id, product_id). Чи це правильно?
codyLine

1
@codyLine: Цей індекс створюється автоматично ПК.
Ервін Брандштеттер

1
@ErwinBrandstetter: Чи не слід створювати індекс на bill_product для стовпця product_id?
Крістіан

2
@ ChristianB.Almeida: Так корисно у багатьох випадках. Я додав трохи про індексацію.
Ервін Брандштеттер

1
@Jakov: У кожній купюрі в таблиці є лише 1 рядок bill. Нам потрібна сума за доданий товар у bill_product.
Ервін Брандштеттер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.