SQL без боли — главные запросы
Даже если используешь Prisma — иногда нужно прочитать что-то напрямую через SQL. Плюс понимать что делает ORM под капотом — критично для отладки. Разберём базовые запросы которых хватит на 80% случаев.
SELECT — главный запрос
взять из таблицы перечисленные поля. SELECT * — все поля (для отладки ОК, для прода — называй явно). WHERE условие — фильтр:
ORDER BY поле DESC — сортировка. LIMIT 10 — взять только первые 10. OFFSET 20 — пропустить первые 20 (для пагинации).
INSERT, UPDATE, DELETE
создать запись.
обновить.
удалить. ВАЖНО: UPDATE и DELETE без WHERE — апдейтят/удаляют ВСЁ. Самая болезненная ошибка новичка — забыть WHERE и убить всю таблицу.
JOIN — соединение таблиц
У тебя User и Order, в Order есть user_id. Хочешь имя пользователя рядом с заказом — JOIN.
Виды: INNER JOIN (только совпавшие в обеих), LEFT JOIN (все из левой + совпавшие из правой, если нет — NULL). 95% задач — INNER или LEFT.
GROUP BY и агрегаты
Хочешь «сколько заказов у каждого пользователя»?
Агрегаты: COUNT, SUM, AVG, MIN, MAX. После GROUP BY каждое НЕ-агрегатное поле должно быть в GROUP BY. HAVING — фильтр после агрегации (как WHERE, но для результата группировки): HAVING COUNT(*) > 5.
Индексы — почему запрос медленный
Без индекса по полю — БД сканирует ВСЮ таблицу при WHERE/JOIN/ORDER BY. На 100 записях ОК, на миллионе — секунды. Индекс — это «оглавление» по полю, делает поиск мгновенным. Создаётся:
На UNIQUE-полях индекс уже есть. На полях по которым часто WHERE/JOIN — добавляй сам.
-- === ТАБЛИЦЫ ===
-- user(id, name, email, age, city, created_at)
-- order(id, user_id, total, status, created_at)
-- product(id, name, price)
-- order_item(id, order_id, product_id, qty)
-- === SELECT базовый ===
SELECT name, email FROM user;
SELECT * FROM user; -- все поля
-- WHERE
SELECT * FROM user WHERE age >= 18;
SELECT * FROM user WHERE city = 'Львов' AND age >= 18;
SELECT * FROM user WHERE name LIKE 'Ал%'; -- начинается на Ал
SELECT * FROM user WHERE city IN ('Львов', 'Киев');
SELECT * FROM user WHERE email IS NULL; -- БЕЗ email
SELECT * FROM user WHERE created_at > NOW() - INTERVAL '7 days'; -- за неделю
-- ORDER + LIMIT (пагинация)
SELECT * FROM user
ORDER BY created_at DESC
LIMIT 20 OFFSET 0; -- первая страница, 20 на стр
SELECT * FROM user
ORDER BY created_at DESC
LIMIT 20 OFFSET 20; -- вторая страница
-- === INSERT / UPDATE / DELETE ===
INSERT INTO user (name, email, age) VALUES ('Алексей', 'a@e.com', 28);
INSERT INTO user (name, email) VALUES -- много за раз
('Иван', 'i@e.com'),
('Мария', 'm@e.com');
UPDATE user SET city = 'Львов' WHERE id = 1;
UPDATE user SET age = age + 1; -- ⚠️ БЕЗ WHERE — всем!
DELETE FROM user WHERE id = 5;
DELETE FROM user; -- ⚠️ ВСЕХ удалит
-- === JOIN ===
-- Заказы с именами клиентов:
SELECT o.id, o.total, u.name, u.email
FROM "order" o
JOIN user u ON o.user_id = u.id
WHERE o.status = 'paid';
-- LEFT JOIN — даже пользователи без заказов:
SELECT u.name, COUNT(o.id) as orders_count
FROM user u
LEFT JOIN "order" o ON o.user_id = u.id
GROUP BY u.id, u.name;
-- === GROUP BY и агрегаты ===
-- Сумма заказов по городу:
SELECT u.city, SUM(o.total) as total_revenue
FROM user u
JOIN "order" o ON o.user_id = u.id
WHERE o.status = 'paid'
GROUP BY u.city
ORDER BY total_revenue DESC;
-- Только города с >10 заказами:
SELECT u.city, COUNT(o.id) as cnt
FROM user u
JOIN "order" o ON o.user_id = u.id
GROUP BY u.city
HAVING COUNT(o.id) > 10;
-- === ИНДЕКСЫ ===
CREATE INDEX idx_user_email ON user(email); -- быстрый поиск по email
CREATE INDEX idx_order_user_id ON "order"(user_id); -- быстрый JOIN
CREATE INDEX idx_order_status ON "order"(status); -- быстрый WHERE
-- === EXPLAIN — посмотреть как БД выполняет запрос ===
EXPLAIN SELECT * FROM user WHERE email = 'a@e.com';
-- Покажет: Seq Scan (полный перебор) или Index Scan (по индексу)
-- Если Seq Scan на большой таблице — нужен индексЗапросы которые встретятся на любой работе с БД