LEARN X · ЗА 14 МИН

SQL

SQL за 14 минут: CREATE, INSERT, SELECT, WHERE, JOIN, GROUP BY, подзапросы, CTE, оконные функции — весь язык запросов на одной странице.

Весь SQL — язык запросов к реляционным базам — на одной странице. Почти без прозы: всё объяснено прямо в комментариях рабочего кода. Примеры на диалекте, близком к PostgreSQL; мелкие отличия от MySQL/SQLite отмечены в комментариях.

1. Создание таблиц

Таблица — это набор строк со столбцами фиксированных типов. Комментарии в SQL начинаются с -- (однострочный) или заключаются в /* ... */ (блочный).

-- Однострочный комментарий
/* Блочный
   комментарий */

-- CREATE TABLE создаёт таблицу. Имя_столбца ТИП [ограничения]
CREATE TABLE authors (
    id          SERIAL PRIMARY KEY,   -- автоинкремент (MySQL: AUTO_INCREMENT)
    name        VARCHAR(100) NOT NULL, -- строка до 100 символов, обязательна
    country     VARCHAR(50),           -- может быть NULL (нет значения)
    born_year   INT                    -- целое число
);

CREATE TABLE books (
    id          SERIAL PRIMARY KEY,
    title       VARCHAR(200) NOT NULL,
    author_id   INT REFERENCES authors(id), -- внешний ключ на authors.id
    price       NUMERIC(8, 2),  -- число с фикс. точностью: до 8 цифр, 2 после точки
    pages       INT,
    published   DATE,           -- дата, напр. '2021-05-30'
    in_stock    BOOLEAN DEFAULT TRUE -- логический тип, по умолчанию TRUE
);

-- Частые типы: INTEGER/INT, BIGINT, NUMERIC/DECIMAL, REAL/DOUBLE,
--              VARCHAR(n), TEXT, BOOLEAN, DATE, TIMESTAMP
-- Частые ограничения: PRIMARY KEY, NOT NULL, UNIQUE, DEFAULT, CHECK, REFERENCES

2. Вставка данных

-- INSERT INTO таблица (столбцы) VALUES (значения);
INSERT INTO authors (name, country, born_year) VALUES
    ('Джордж Оруэлл', 'Великобритания', 1903),
    ('Рэй Брэдбери',  'США',            1920),
    ('Фёдор Достоевский', 'Россия',     1821); -- несколько строк за раз

INSERT INTO books (title, author_id, price, pages, published) VALUES
    ('1984',                 1, 599.00, 328, '1949-06-08'),
    ('Скотный двор',         1, 399.50, 112, '1945-08-17'),
    ('451 градус по Фаренгейту', 2, 549.00, 256, '1953-10-19'),
    ('Преступление и наказание', 3, 720.00, 671, '1866-01-01'),
    ('Марсианские хроники',  2, 480.00, 222, '1950-05-04');

-- Можно опускать столбцы со значением по умолчанию или NULL:
INSERT INTO books (title, author_id) VALUES ('Черновик без цены', 3);
-- price, pages, published станут NULL; in_stock = TRUE (DEFAULT)

3. Выборка

-- SELECT столбцы FROM таблица;
SELECT id, title, price FROM books; -- только указанные столбцы

SELECT * FROM authors; -- * = все столбцы

-- AS задаёт псевдоним (alias) столбцу или таблице
SELECT title AS название,
       price AS цена_руб
FROM books;

-- Вычисляемые столбцы
SELECT title,
       price,
       price * 0.9 AS цена_со_скидкой -- арифметика прямо в выборке
FROM books;

-- DISTINCT убирает дубликаты
SELECT DISTINCT country FROM authors;
-- → Великобритания, США, Россия (каждая страна один раз)

-- Конкатенация строк: || (Postgres/SQLite), CONCAT(...) (MySQL)
SELECT name || ' (' || country || ')' AS подпись FROM authors;
-- → Джордж Оруэлл (Великобритания)

4. Фильтрация

-- WHERE отбирает строки по условию
SELECT title, price FROM books WHERE price > 500;

-- Операторы сравнения: = <> (или !=) < > <= >=
SELECT * FROM books WHERE pages >= 300;

-- Логические: AND, OR, NOT
SELECT * FROM books
WHERE price < 600 AND pages < 300;

SELECT * FROM books
WHERE author_id = 1 OR author_id = 2;

-- LIKE: шаблон. % = любое число символов, _ = ровно один символ
SELECT title FROM books WHERE title LIKE '1%';   -- начинается с '1' → '1984'
SELECT title FROM books WHERE title LIKE '%хроники'; -- кончается на 'хроники'
SELECT name  FROM authors WHERE name LIKE '_эй%'; -- 2-я буква 'э' → Рэй Брэдбери
-- ILIKE — регистронезависимый поиск (Postgres)

-- IN: значение из списка (короче, чем цепочка OR)
SELECT * FROM books WHERE author_id IN (1, 3);

-- BETWEEN: диапазон включительно (a <= x <= b)
SELECT title, price FROM books WHERE price BETWEEN 400 AND 600;

-- IS NULL / IS NOT NULL — проверка на отсутствие значения.
-- ВАЖНО: '= NULL' не работает, всегда используйте IS NULL
SELECT title FROM books WHERE published IS NULL;
SELECT title FROM books WHERE price IS NOT NULL;

5. Сортировка и лимит

-- ORDER BY сортирует. ASC — по возрастанию (по умолчанию), DESC — по убыванию
SELECT title, price FROM books ORDER BY price DESC;

-- Сортировка по нескольким столбцам (сначала по 1-му, при равенстве — по 2-му)
SELECT title, author_id, price
FROM books
ORDER BY author_id ASC, price DESC;

-- LIMIT ограничивает число строк. OFFSET пропускает первые N строк
SELECT title, price FROM books ORDER BY price DESC LIMIT 3; -- топ-3 дорогих

-- Пагинация: страница 2 по 2 записи (пропустить 2, взять 2)
SELECT title FROM books ORDER BY id LIMIT 2 OFFSET 2;
-- MySQL также допускает: LIMIT 2, 2  (offset, count)
-- SQL Server: OFFSET 2 ROWS FETCH NEXT 2 ROWS ONLY

6. Агрегатные функции

Агрегатные функции схлопывают множество строк в одно значение.

SELECT COUNT(*)     AS всего_книг   FROM books; -- число строк → 6
SELECT COUNT(price) AS с_ценой      FROM books; -- COUNT(столбец) игнорирует NULL
SELECT SUM(price)   AS сумма_цен    FROM books; -- сумма
SELECT AVG(price)   AS средняя_цена FROM books; -- среднее (NULL пропускаются)
SELECT MIN(price)   AS самая_дешёвая FROM books; -- минимум
SELECT MAX(pages)   AS макс_страниц  FROM books; -- максимум

-- Несколько агрегатов в одном запросе
SELECT COUNT(*)            AS книг,
       ROUND(AVG(price),2) AS средняя,
       MAX(price)          AS дороже_всех
FROM books
WHERE in_stock = TRUE;

7. Группировка

-- GROUP BY разбивает строки на группы; агрегат считается для каждой группы
SELECT author_id,
       COUNT(*)   AS число_книг,
       AVG(price) AS средняя_цена
FROM books
GROUP BY author_id;
-- → по одной строке на каждого author_id с его статистикой

-- HAVING фильтрует УЖЕ СГРУППИРОВАННЫЕ данные (WHERE — до группировки)
SELECT author_id, COUNT(*) AS число_книг
FROM books
GROUP BY author_id
HAVING COUNT(*) >= 2; -- только авторы с 2+ книгами

-- Порядок выполнения: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
SELECT author_id, SUM(price) AS оборот
FROM books
WHERE in_stock = TRUE      -- фильтр строк ДО группировки
GROUP BY author_id
HAVING SUM(price) > 800     -- фильтр групп ПОСЛЕ группировки
ORDER BY оборот DESC;

8. Соединения

JOIN объединяет строки из разных таблиц по условию (обычно по внешнему ключу).

-- INNER JOIN: только совпавшие пары строк из обеих таблиц
SELECT b.title, a.name AS автор
FROM books AS b
INNER JOIN authors AS a ON b.author_id = a.id;
-- → '1984' | Джордж Оруэлл, '451 градус...' | Рэй Брэдбери, ...
-- Книга без author_id в результат НЕ попадёт

-- LEFT JOIN: ВСЕ строки слева + совпадения справа (нет пары → NULL справа)
SELECT a.name, b.title
FROM authors AS a
LEFT JOIN books AS b ON b.author_id = a.id;
-- → автор без книг тоже попадёт, у него b.title = NULL

-- Найти авторов без книг: LEFT JOIN + проверка на NULL
SELECT a.name
FROM authors AS a
LEFT JOIN books AS b ON b.author_id = a.id
WHERE b.id IS NULL;

-- RIGHT JOIN: зеркало LEFT — все строки СПРАВА + совпадения слева
SELECT a.name, b.title
FROM authors AS a
RIGHT JOIN books AS b ON b.author_id = a.id;
-- → все книги, даже если автор не найден (a.name = NULL)
-- SQLite не поддерживает RIGHT JOIN — поменяйте таблицы местами и используйте LEFT

-- JOIN + агрегаты: сколько книг у каждого автора
SELECT a.name, COUNT(b.id) AS книг
FROM authors AS a
LEFT JOIN books AS b ON b.author_id = a.id
GROUP BY a.name
ORDER BY книг DESC;

9. Подзапросы и CTE

-- Подзапрос в WHERE: книги дороже средней цены
SELECT title, price
FROM books
WHERE price > (SELECT AVG(price) FROM books); -- внутренний запрос → одно число

-- Подзапрос с IN: книги авторов из США
SELECT title
FROM books
WHERE author_id IN (SELECT id FROM authors WHERE country = 'США');

-- Подзапрос в FROM (производная таблица): обязателен псевдоним
SELECT автор, книг
FROM (
    SELECT a.name AS автор, COUNT(b.id) AS книг
    FROM authors a LEFT JOIN books b ON b.author_id = a.id
    GROUP BY a.name
) AS сводка
WHERE книг > 1;

-- CTE (Common Table Expression) через WITH — то же, но читабельнее.
-- Именованный временный результат, на который можно ссылаться ниже
WITH stats AS (
    SELECT author_id, COUNT(*) AS книг, SUM(price) AS оборот
    FROM books
    GROUP BY author_id
)
SELECT a.name, s.книг, s.оборот
FROM stats s
JOIN authors a ON a.id = s.author_id
WHERE s.оборот > 500
ORDER BY s.оборот DESC;

-- Несколько CTE через запятую:
WITH дорогие AS (SELECT * FROM books WHERE price > 500),
     толстые AS (SELECT * FROM books WHERE pages > 300)
SELECT title FROM дорогие
UNION
SELECT title FROM толстые;

10. Объединение

-- UNION склеивает результаты двух SELECT (число и типы столбцов должны совпадать).
-- UNION убирает дубликаты строк
SELECT title FROM books WHERE price > 600
UNION
SELECT title FROM books WHERE pages > 300;

-- UNION ALL оставляет ВСЕ строки, включая дубли (быстрее — не дедуплицирует)
SELECT country FROM authors WHERE born_year < 1910
UNION ALL
SELECT country FROM authors WHERE born_year > 1915;

-- Удобно помечать источник строк литералом
SELECT title, 'дорогая' AS метка FROM books WHERE price > 600
UNION ALL
SELECT title, 'дешёвая' AS метка FROM books WHERE price <= 600;

-- Родственники: INTERSECT (пересечение), EXCEPT/MINUS (разность)

11. Изменение данных

-- UPDATE меняет существующие строки. БЕЗ WHERE обновятся ВСЕ строки!
UPDATE books
SET price = price * 1.1   -- поднять цену на 10%
WHERE author_id = 1;

-- Можно менять несколько столбцов сразу
UPDATE books
SET price = 999.00,
    in_stock = FALSE
WHERE title = '1984';

-- DELETE удаляет строки. БЕЗ WHERE удалятся ВСЕ строки!
DELETE FROM books WHERE price IS NULL;

DELETE FROM books WHERE author_id NOT IN (SELECT id FROM authors);

-- TRUNCATE быстро очищает всю таблицу (без условий, без журналирования каждой строки)
-- TRUNCATE TABLE books;

12. Изменение схемы и индексы

-- ALTER TABLE меняет структуру существующей таблицы
ALTER TABLE books ADD COLUMN genre VARCHAR(50); -- добавить столбец
ALTER TABLE books ALTER COLUMN price SET NOT NULL; -- сделать обязательным (Postgres)
ALTER TABLE books RENAME COLUMN genre TO category; -- переименовать столбец
ALTER TABLE books DROP COLUMN category;            -- удалить столбец
ALTER TABLE books RENAME TO catalog;               -- переименовать таблицу
ALTER TABLE catalog RENAME TO books;               -- вернём обратно

-- CREATE INDEX ускоряет поиск/сортировку по столбцу (ценой места и записи)
CREATE INDEX idx_books_author ON books(author_id);
CREATE INDEX idx_books_price  ON books(price);
CREATE UNIQUE INDEX idx_authors_name ON authors(name); -- ещё и запрещает дубли

-- DROP удаляет объекты целиком
DROP INDEX idx_books_price;        -- удалить индекс (Postgres/SQLite)
DROP TABLE IF EXISTS old_logs;     -- удалить таблицу, если существует
-- IF EXISTS подавляет ошибку, когда объекта нет

13. Оконные функции

Оконные функции считают агрегаты по «окну» строк, НЕ схлопывая их в одну (в отличие от GROUP BY каждая строка сохраняется).

-- OVER() задаёт окно. PARTITION BY делит на группы, ORDER BY задаёт порядок внутри.

-- ROW_NUMBER() — сквозной номер строки (1,2,3,...) внутри каждой партиции
SELECT title, author_id, price,
       ROW_NUMBER() OVER (PARTITION BY author_id ORDER BY price DESC) AS номер
FROM books;
-- → у каждого автора своя нумерация книг от самой дорогой к дешёвой

-- RANK() — ранг с пропусками при равенстве (1,1,3); DENSE_RANK() — без пропусков (1,1,2)
SELECT title, price,
       RANK()       OVER (ORDER BY price DESC) AS ранг,
       DENSE_RANK() OVER (ORDER BY price DESC) AS плотный_ранг
FROM books;

-- Агрегат как оконная функция: видно и строку, и итог по её группе одновременно
SELECT title, author_id, price,
       SUM(price) OVER (PARTITION BY author_id) AS оборот_автора,
       AVG(price) OVER () AS средняя_по_всем
FROM books;

-- Типичный приём: «взять топ-1 в каждой группе» через нумерацию в CTE
WITH ranked AS (
    SELECT title, author_id, price,
           ROW_NUMBER() OVER (PARTITION BY author_id ORDER BY price DESC) AS rn
    FROM books
)
SELECT title, author_id, price
FROM ranked
WHERE rn = 1; -- самая дорогая книга каждого автора
Поддержать проект