Реляційна модель і перший SELECT
SQL з'явився у 1974-му. Відтоді змінилося все: мови, платформи, підходи — а SQL лишився. Причина в тому, що він описує що треба отримати, а не як це зробити. Оптимізатор бази сам вирішує «як», і робить це краще за більшість людей.
Реляційна модель за п'ять хвилин
Дані живуть у таблицях. Таблиця — набір рядків однакової структури.
courses
┌────┬────────────────┬───────┬─────────────┐
│ id │ title │ price │ level │
├────┼────────────────┼───────┼─────────────┤
│ 1 │ HTML і CSS │ 249 │ Початковий │
│ 2 │ C# та .NET │ 2499 │ Junior │
│ 3 │ Сервер MMORPG │ 3999 │ Senior │
└────┴────────────────┴───────┴─────────────┘
- Стовпець має тип і назву
- Рядок — один запис
- Первинний ключ (
id) унікально визначає рядок - Зовнішній ключ — посилання на рядок іншої таблиці
Ключова ідея: порядок рядків не визначений. Якщо тобі потрібен порядок — вимагай його явно через ORDER BY. Покладатися на те, що «вони й так виходять правильно», не можна: сьогодні так, а після зміни плану запиту — інакше.
Типи даних
CREATE TABLE courses (
id SERIAL PRIMARY KEY, -- автоінкремент (Postgres)
slug VARCHAR(64) NOT NULL UNIQUE,
title VARCHAR(200) NOT NULL,
description TEXT,
price INTEGER NOT NULL, -- копійки
level VARCHAR(32) NOT NULL DEFAULT 'Початковий',
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Кілька рішень, які варто приймати свідомо:
Гроші — цілим числом у копійках. FLOAT для грошей дає похибку округлення, яка з часом накопичується в реальні розбіжності. Якщо дуже треба дробові — NUMERIC(10,2), ніколи не FLOAT.
NOT NULL за замовчуванням. NULL — не «порожньо», а «невідомо», і він отруює всі порівняння. Дозволяй NULL лише там, де «невідомо» — осмислений стан.
VARCHAR(n) з розумною межею. Не VARCHAR(255) за звичкою: обмеження — це документація того, що ти очікуєш.
SELECT
SELECT * FROM courses;
SELECT title, price FROM courses;
SELECT title AS назва, price / 100 AS ціна_грн FROM courses;
SELECT * зручний у консолі й шкідливий у коді застосунку: додасться стовпець — зміниться результат, і те, що покладалося на порядок полів, зламається. У продакшен-коді перелічуй стовпці явно.
NULL: головна пастка новачка
SELECT * FROM courses WHERE description = NULL; -- ✗ поверне 0 рядків ЗАВЖДИ
SELECT * FROM courses WHERE description IS NULL; -- ✓ правильно
NULL не дорівнює нічому, включно з самим собою. NULL = NULL дає не TRUE і не FALSE, а NULL.
SELECT COALESCE(description, 'Без опису') FROM courses; -- підставити значення
SELECT NULLIF(price, 0); -- зробити NULL, якщо 0
Порядок виконання
Пишемо ми в одному порядку, а виконується інший:
FROM → звідки беремо
WHERE → відсіюємо рядки
GROUP BY → групуємо
HAVING → відсіюємо групи
SELECT → обчислюємо стовпці
ORDER BY → сортуємо
LIMIT → обмежуємо
Це пояснює річ, об яку спотикаються всі:
SELECT price / 100 AS uah FROM courses WHERE uah > 10; -- ✗ помилка
WHERE виконується до SELECT, тож псевдоніма uah там ще не існує. Правильно:
SELECT price / 100 AS uah FROM courses WHERE price / 100 > 10;
А от в ORDER BY псевдонім працює — він виконується після SELECT.
Вставка, зміна, видалення
INSERT INTO courses (slug, title, price, level)
VALUES ('sql-databases', 'SQL з нуля', 59900, 'Початковий');
INSERT INTO courses (slug, title, price, level) VALUES
('go-basic', 'Go з нуля', 199900, 'Початковий'),
('rust-basic', 'Rust', 299900, 'Середній');
UPDATE courses SET price = 49900 WHERE slug = 'sql-databases';
DELETE FROM courses WHERE published = FALSE;
⚠ UPDATE і DELETE без WHERE зачіпають усю таблицю. Це найдорожча одруківка в професії. Звичка, яка рятує: спершу напиши той самий запит як SELECT, подивись, що він поверне, і лише потім заміни на UPDATE.
-- 1. Перевірити
SELECT * FROM courses WHERE published = FALSE;
-- 2. І тільки потім
DELETE FROM courses WHERE published = FALSE;
Практика
- Постав PostgreSQL (або MySQL) локально чи через Docker.
- Створи таблицю
coursesза прикладом. - Додай 10 рядків, з них два з
NULLв описі. - Спробуй знайти рядки з порожнім описом через
= NULLі черезIS NULL— порівняй результат. - Напиши
SELECT, що виводить назву й ціну в гривнях із псевдонімами.
Підсумок
- Порядок рядків не гарантований без
ORDER BY - Гроші — цілі копійки, не
FLOAT NOT NULLза замовчуванням;NULL= «невідомо», не «порожньо»- Порівняння з
NULL— тількиIS NULL WHEREвиконується доSELECT, тому псевдоніми там недоступніSELECT *— для консолі, не для коду- Перед
DELETE/UPDATEвиконай той самийWHEREякSELECT
Далі — фільтрація й сортування.