EvolCode
треки/Сервер и продакшн·03 / 08
7 мин чтения

SQL без боли — главные запросы

Даже если используешь Prisma — иногда нужно прочитать что-то напрямую через SQL. Плюс понимать что делает ORM под капотом — критично для отладки. Разберём базовые запросы которых хватит на 80% случаев.

SELECT — главный запрос

$ SELECT поле1, поле2 FROM таблица

взять из таблицы перечисленные поля. SELECT * — все поля (для отладки ОК, для прода — называй явно). WHERE условие — фильтр:

$ WHERE age > 18 AND city = "Львов"

ORDER BY поле DESC — сортировка. LIMIT 10 — взять только первые 10. OFFSET 20 — пропустить первые 20 (для пагинации).

INSERT, UPDATE, DELETE

$ INSERT INTO users (name, email) VALUES ("Ан", "a@e.com")

создать запись.

$ UPDATE users SET name = "Алексей" WHERE id = 5

обновить.

$ DELETE FROM users WHERE id = 5

удалить. ВАЖНО: UPDATE и DELETE без WHERE — апдейтят/удаляют ВСЁ. Самая болезненная ошибка новичка — забыть WHERE и убить всю таблицу.

JOIN — соединение таблиц

У тебя User и Order, в Order есть user_id. Хочешь имя пользователя рядом с заказом — JOIN.

$ SELECT order.id, user.name FROM order JOIN user ON order.user_id = user.id

Виды: INNER JOIN (только совпавшие в обеих), LEFT JOIN (все из левой + совпавшие из правой, если нет — NULL). 95% задач — INNER или LEFT.

GROUP BY и агрегаты

Хочешь «сколько заказов у каждого пользователя»?

$ SELECT user_id, COUNT(*) FROM order GROUP BY user_id

Агрегаты: COUNT, SUM, AVG, MIN, MAX. После GROUP BY каждое НЕ-агрегатное поле должно быть в GROUP BY. HAVING — фильтр после агрегации (как WHERE, но для результата группировки): HAVING COUNT(*) > 5.

Индексы — почему запрос медленный

Без индекса по полю — БД сканирует ВСЮ таблицу при WHERE/JOIN/ORDER BY. На 100 записях ОК, на миллионе — секунды. Индекс — это «оглавление» по полю, делает поиск мгновенным. Создаётся:

$ CREATE INDEX idx_user_email ON user(email)

На UNIQUE-полях индекс уже есть. На полях по которым часто WHERE/JOIN — добавляй сам.

примерsql
-- === ТАБЛИЦЫ ===
-- 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 на большой таблице — нужен индекс

Запросы которые встретятся на любой работе с БД

главное
  • 01SELECT поле, ... FROM таблица — основа всех чтений
  • 02WHERE условие фильтрует, ORDER BY сортирует, LIMIT/OFFSET — пагинация
  • 03UPDATE/DELETE без WHERE = катастрофа, ВСЕГДА проверяй WHERE
  • 04JOIN соединяет таблицы; LEFT JOIN — все записи слева + совпавшие справа
  • 05GROUP BY + COUNT/SUM/AVG = аналитика; HAVING — фильтр результата группировки
  • 06Индексы на полях по которым WHERE/JOIN — мастхев на больших таблицах
  • 07EXPLAIN показывает план запроса — главный инструмент диагностики тормозов
проверь себя
01Запрос `DELETE FROM users` без WHERE. Что произойдёт?
02Какая разница между INNER JOIN и LEFT JOIN?
03Запрос медленный на большой таблице. С чего начать?
следующий урок: Аутентификация: пароли, сессии, JWT