Як ми вже обговорювали, PostgreSQL відноситься до реляційних баз даних. Реляційні БД характеризуються наявністю таблиць та відносин.
Існує 3 типи відносин:
- "Один до одного" - коли один запис у першій таблиці відповідає одному запису у другій таблиці. Зустрічається такий зв'язок досить рідко. Така зв'язок чи надмірна, тобто. може сенс просто об'єднати дані в одну таблицю, або це результат модернізації архітектури, і таке рішення кимось прийнято обґрунтовано.
- "Один до багатьох" - коли одного запису в першій таблиці відповідає кілька записів в іншій таблиці. Наприклад, клієнт магазину може мати кілька номерів. Один клієнт – один запис у таблиці клієнта, його номери телефонів – записи у таблиці телефонів. Один клієнт відноситься до багатьох номерів телефонів, але зворотний зв'язок - номер телефону до клієнта - багато хто до одного.
- "Багато до багатьох" - коли одному запису в першій таблиці відповідає кілька записів у другій таблиці, але одному запису другої таблиці може відповідати кілька записів першої таблиці. Класичний приклад - книги та автори, адже автор може мати багато книг, і кожна книга може мати кілька авторів. Реалізується з допомогою створення таблиці зв'язків, куди копіюються ключі записів обох таблиць.

Створимо таблиці авторів, книг, жанрів та таблицю зв'язків для авторів та книг (багато хто до багатьох):
CREATE TABLE authors (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL DEFAULT 'lore',
year DATE NOT NULL DEFAULT '1970-01-01'
);
CREATE TABLE
CREATE TABLE books (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL DEFAULT 'noname',
genre_id INT NOT NULL DEFAULT 0
);
CREATE TABLE
CREATE TABLE genres (
id SERIAL PRIMARY KEY,
genre VARCHAR(100) NOT NULL DEFAULT 'unknown'
);
CREATE TABLE
CREATE TABLE authors_books (
author_id INT NOT NULL DEFAULT 0,
book_id INT NOT NULL DEFAULT 0
);
CREATE TABLEДалі слід заповнити таблицю даними:
INSERT INTO genres (genre) VALUES
('SF'),
('novel'),
('story'),
('horror');
INSERT INTO books(title, genre_id) VALUES
('Майстер і Маргарита', 2),
('Фауст', 0),
('Білий клик', 3),
('Дюна', 1),
('Війна і мир', 2);
INSERT INTO authors (name) VALUES
('Френк Герберт'),
('Михайло Булгаков'),
('Джек Лондон'),
('Йоган Ґете'),
('Роберт Хайнлайн');
INSERT INTO authors_books (author_id, book_id) VALUES
(1, 4),
(2, 1),
(3, 3),
(4, 2);
Дані готові, тепер можна вивчати об'єднання таблиць. Почнемо ми з об'єднання двох таблиць в одному запиті:
SELECT title, genre
FROM books
INNER JOIN genres ON (genres.id = books.genre_id);
title | genre
---------------------+-------
Майстер і Маргарита | novel
Білий клик | story
Дюна | SF
Війна і мир | novel
(4 rows)
INNER JOIN виводить записи з лівої таблиці (першої з двох таблиць, які він об'єднує), для яких знайдеться відповідний запис у правій (другій та останній у списку) таблиці. Якщо відповідності у правій таблиці немає, такий запис не виводиться. В даному випадку можна бачити, що запис про книгу "Фауст" не виводиться. Це відбувається через те, що genre_id цей запис дорівнює нулю, а такого жанру в таблиці жанрів немає.
Так само в результат не потрапляє жанр horror, тому що немає жодної книги з таким жанром.
SELECT title, genre
FROM books
LEFT JOIN genres ON (genres.id = books.genre_id);
title | genre
---------------------+-------
Майстер і Маргарита | novel
Фауст |
Білий клик | story
Дюна | SF
Війна і мир | novel
(5 rows)
LEFT JOIN виводить ВСІ записи з лівої таблиці. Для тих записів, яким знаходиться відповідність у правій, він, аналогічно INNER JOIN, виведе відповідні дані з другої таблиці. Для тих, яким відповідності не знайшлося, він нічого не виведе в стовпцях правої таблиці, залишивши їх порожніми.
Щоб визначити, яким книгам не знайдені у відповідність жанри, можна виконати запит із підзапитом:
SELECT title
FROM books
WHERE NOT EXISTS (
SELECT *
FROM genres
WHERE books.genre_id = genres.id
);
title
-------
Фауст
(1 row)Цей шлях вважається правильнішим, ніж класичний:
SELECT title, genre
FROM books
LEFT JOIN genres ON (genres.id = books.genre_id)
WHERE genre IS NULL;
title
-------
Фауст
(1 row)
RIGHT JOIN надходить аналогічним чином з правою таблицею: виводить усі записи з неї, додаючи записи з лівої. Де відповідності немає, залишає в стовпцях лівої таблиці порожнечу.
SELECT title, genre
FROM books
RIGHT JOIN genres ON (genres.id = books.genre_id);
title | genre
---------------------+--------
Майстер і Маргарита | novel
Білий клик | story
Дюна | SF
Війна і мир | novel
| horror
(5 rows)Якщо у вас виникне потреба побачити всі книги та всі жанри незалежно від наявності відповідних записів в іншій таблиці, але де зв'язки є – зв'язати таблиці, можна об'єднати результати двох запитів:
SELECT title, genre
FROM books
LEFT JOIN genres ON (genres.id = books.genre_id)
UNION
SELECT title, genre
FROM books RIGHT JOIN genres ON (genres.id = books.genre_id);
title | genre
---------------------+--------
Фауст |
| horror
Війна і мир | novel
Білий клик | story
Дюна | SF
Майстер і Маргарита | novel
(6 rows)Аналогічного результату можна досягти і спеціальним видом об'єднань - FULL JOIN:
SELECT title, genre
FROM books
FULL JOIN genres ON (genres.id = books.genre_id);
title | genre
---------------------+--------
Майстер і Маргарита | novel
Фауст |
Білий клик | story
Дюна | SF
Війна і мир | novel
| horror
(6 rows)Якщо поля зв'язку називаються однаково, наприклад, поле id у таблиці genres називається genre_id, запит можна трохи спростити. Для демонстрації перейменуємо поле та виконаємо цей запит:
ALTER TABLE genres RENAME COLUMN id TO genre_id;
ALTER TABLE
SELECT title, genre
FROM books
RIGHT JOIN genres USING(genre_id);
title | genre
---------------------+--------
Майстер і Маргарита | novel
Білий клик | story
Дюна | SF
Війна і мир | novel
| horror
(5 rows)
Результат буде той же, що і у звичайного ** RIGHT JOIN запиту, але сам запит коротше.
- Об'єднати авторів із книгами за допомогою таблиці зв'язків
- Побудувати фамільне дерево по чоловічій лінії


