Видове връзки: 1:1, 1:N, M:N
Накратко: Връзката между две таблици е едно към едно, едно към много или много към много, и от вида ѝ зависи къде е външният ключ и кога трябва отделна таблица за двойките.
Проблем
Карта и етикети
Магазинът пуска две новости. Първата е карта за лоялност: всеки клиент може да има една карта с точки. Втората са етикети на продуктите, като „веган“, „без глутен“ и „био“, за да ги филтрираш в сайта.
Къде да ги запишеш? Колона card_number в customers? Колона tags в products с текст „веган, био“? Отговорът зависи от един въпрос: колко неща от едната страна отговарят на колко от другата.
Идеята
Едно към много (1:N)
Един клиент има много поръчки. Всяка поръчка е на един клиент. Това е връзка едно към много, пише се 1:N.
Ключът е от страната на „много“-то. Всяка поръчка казва чия е с customer_id:
customers
- PK
idinteger nametext *emailtext *citytextjoined_attext *
orders
- PK
idinteger - FK
customer_idinteger *→ customers ordered_attext *statustext *
Обратното не става. Колко колони order_1, order_2 да има клиентът? Мария има 12 поръчки, а утре ще са 13.
Това е най-честата връзка. В магазина са такива и категорията с продуктите, и продуктът с отзивите.
Пробвай
Продуктите по категории
Колко продукта има във всяка категория, в която има продукти? Покажи името на категорията и броя.
Решение
SELECT c.name, COUNT(*) AS products
FROM categories c
JOIN products p ON p.category_id = c.id
GROUP BY c.id, c.name;Категорията и продуктите са 1:N, затова свързваш products.category_id с categories.id и групираш по категория. Излизат 6 категории. „Храни“ и „Дом“ ги няма: те са главни категории и в тях няма продукти направо, само подкатегории.
Идеята
Едно към едно (1:1)
Всеки клиент има най-много една карта, а всяка карта е на един клиент. Това е едно към едно, 1:1. Пази се като 1:N, с ключ към клиента, и с още едно правило: UNIQUE.
loyalty_cards
- PK
idinteger - FK
customer_idinteger *→ customers numbertext *pointsinteger *issued_attext *
Пет клиенти имат карта. Опитай да дадеш на Мария втора:
UNIQUE на customer_id не пуска втори ред за същия клиент. Без него връзката щеше да е 1:N.
Защо не колони в customers? Може и така. Често 1:1 са още няколко колони в същата таблица. Отделна таблица има смисъл, когато повечето редове нямат втората част (15 от 20 клиенти нямат карта и щяха да имат празни клетки) или когато тя има свой живот: картата се губи, сменя се, има свои точки.
Пробвай
Кой още няма карта
Маркетингът иска да предложи карта на всеки клиент, който още няма. Покажи имената им.
Решение
SELECT c.name
FROM customers c
LEFT JOIN loyalty_cards l ON l.customer_id = c.id
WHERE l.id IS NULL;LEFT JOIN пази всички клиенти, а при тези без карта колоните на loyalty_cards са NULL. WHERE l.id IS NULL оставя само тях: 15 клиенти.
Идеята
Много към много (M:N)
В една поръчка има много продукти. Един продукт е в много поръчки. Това е много към много, M:N.
Тук ключът не може да е в нито една от двете таблици. Решението е трета таблица, таблица за връзка, с по един ред за всяка двойка. В магазина това е order_items: всеки ред е един продукт в една поръчка, с броя и цената.
products
- PK
idinteger nametext *- FK
category_idinteger→ categories pricereal *stockinteger *added_attext *
order_items
- PK
order_idinteger→ orders - PK
product_idinteger→ products quantityinteger *unit_pricereal *
В кои поръчки е кафето (продукт 17)?
Етикетите са същото. Продуктът има много етикети, а етикетът е на много продукти:
tags
- PK
idinteger nametext *
product_tags
- PK
product_idinteger→ products - PK
tag_idinteger→ tags
Виж го
Двойката е ключът
Първичният ключ на order_items е двойката (order_id, product_id). Опитай да сложиш хляба втори път в поръчка 60, където вече го има:
Базата не пуска същата двойка два пъти. Продукт е в поръчката веднъж, а повече бройки се пишат в quantity. Иначе отчетът „колко пъти е поръчан хлябът“ щеше да брои един и същ продукт от една поръчка два пъти.
Пробвай
Етикетите в сайта
До всеки етикет в сайта пише колко продукта има. Покажи името на етикета и броя.
Решение
SELECT t.name, COUNT(*) AS products
FROM tags t
JOIN product_tags pt ON pt.tag_id = t.id
GROUP BY t.id, t.name;Групираш product_tags по етикет: „веган“ 10, „без глутен“ 11, „био“ 6. Сборът е 27 двойки, а продуктите с етикет са 13, защото повечето имат повече от един.
Упражнение
Две задачи
1. На страницата на ябълките се показват етикетите им. Кои са?
Етикетите на продукта „Ябълки“:
Решение
SELECT t.name
FROM tags t
JOIN product_tags pt ON pt.tag_id = t.id
JOIN products p ON p.id = pt.product_id
WHERE p.name = 'Ябълки';Същата таблица за връзка, прочетена от другата страна: от продукта към етикетите. „веган“, „без глутен“ и „био“.
2. Клиентка търси продукти, които са и веган, и без глутен. Покажи имената им.
Продуктите с двата етикета, „веган“ и „без глутен“:
Решение
SELECT p.name
FROM products p
JOIN product_tags pt ON pt.product_id = p.id
JOIN tags t ON t.id = pt.tag_id
WHERE t.name IN ('веган', 'без глутен')
GROUP BY p.id, p.name
HAVING COUNT(*) = 2;WHERE t.name = 'веган' AND t.name = 'без глутен' не намира нищо, защото всеки ред има един етикет. Вземаш редовете с единия или другия етикет, групираш по продукт и оставяш тези с COUNT(*) = 2. Излизат 8 продукта: белият хляб е само веган, а сиренето е само без глутен.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: INSERT: нови редове · CREATE TABLE: нова таблица · Ограничения: правила за данните · Външни ключове: връзки между таблиците
- Продължава в: ER диаграма: от описание до схема · Нормализация · Естествен ключ и денормализация · Графови бази
- Задачи по темата: Проектиране на база