Урок 20. SQL на практике
Запросы, которые нужны тестировщику каждый день
Темы урока
SELECT/FROM/WHERE, ORDER BY/LIMIT/DISTINCT, агрегаты (COUNT/SUM/AVG/MIN/MAX), GROUP BY/HAVING, JOIN (INNER/LEFT/RIGHT), INSERT/UPDATE/DELETE
Видео урока
Конспект урока
Главное за урок
SQL — не опциональный скилл для QA. UI может показать «заказ создан», API вернуть 200 — но только SELECT в базе скажет правду: запись появилась, статус сменился, счётчик промокода вырос.
Три кита SQL-запросов для тестировщика: SELECT + WHERE + JOIN. Всё остальное — агрегаты, сортировка, группировка — строится на них.
Структура SQL-запроса
SELECT столбцы -- что выбираем
FROM таблица -- откуда
WHERE условие -- фильтр строк
GROUP BY столбец -- группировка
HAVING условие -- фильтр групп
ORDER BY столбец -- сортировка
LIMIT N; -- ограничение числа строк
Порядок выполнения отличается от порядка написания: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.
SELECT и WHERE — базовые запросы
-- Все строки таблицы
SELECT * FROM orders;
-- Конкретные столбцы
SELECT id, email, name FROM users;
-- Фильтрация
SELECT * FROM orders WHERE status = 'paid';
SELECT * FROM users WHERE email LIKE '%@test.ru';
SELECT * FROM products WHERE price BETWEEN 100 AND 500;
SELECT * FROM orders WHERE status IN ('new', 'paid', 'cancelled');
SELECT * FROM orders WHERE user_id IS NOT NULL;
ORDER BY, LIMIT, DISTINCT
-- Сортировка по возрастанию (по умолчанию)
SELECT * FROM orders ORDER BY created_at ASC;
-- Сортировка по убыванию
SELECT * FROM orders ORDER BY total DESC;
-- Первые 10 строк
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;
-- Уникальные значения
SELECT DISTINCT status FROM orders;
Агрегатные функции
| Функция | Что считает |
|---|---|
COUNT(*) |
Количество строк |
SUM(col) |
Сумма значений |
AVG(col) |
Среднее значение |
MIN(col) |
Минимальное значение |
MAX(col) |
Максимальное значение |
SELECT COUNT(*) FROM orders WHERE status = 'paid';
SELECT AVG(total) FROM orders;
SELECT SUM(total) FROM orders WHERE user_id = 1;
GROUP BY и HAVING
-- Количество заказов по статусам
SELECT status, COUNT(*) AS cnt
FROM orders
GROUP BY status
ORDER BY cnt DESC;
-- WHERE фильтрует ДО группировки, HAVING — ПОСЛЕ
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 1;
JOIN — соединение таблиц
| Тип | Результат |
|---|---|
INNER JOIN |
Только строки с парой в обеих таблицах |
LEFT JOIN |
Все строки левой + совпавшие из правой (или NULL) |
RIGHT JOIN |
Все строки правой + совпавшие из левой (или NULL) |
FULL JOIN |
Все строки обеих таблиц |
-- INNER JOIN: заказы с именами пользователей
SELECT o.id, u.name, o.status
FROM orders o
JOIN users u ON o.user_id = u.id;
-- LEFT JOIN: найти заказы без пользователя (нарушение FK)
SELECT o.id, o.user_id
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.id IS NULL;
INSERT, UPDATE, DELETE
-- Создать тестового пользователя
INSERT INTO users (email, name) VALUES ('test@qa.ru', 'Тест QA');
-- Изменить статус заказа
UPDATE orders SET status = 'cancelled' WHERE id = 42;
-- Удалить тестовые данные (всегда сначала SELECT!)
DELETE FROM users WHERE email = 'test@qa.ru';
SQL-кейсы QA: что смотреть в базе
| Тест-кейс | SQL-запрос |
|---|---|
| Регистрация | SELECT * FROM users WHERE email = 'user@mail.ru' |
| Оплата | SELECT status FROM orders WHERE id = 42 |
| Промокод | SELECT used_count, max_uses FROM promo_codes WHERE code = 'QA60' |
| Нарушение FK | SELECT o.id FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE u.id IS NULL |
Ключевые тезисы для теста
SELECT *возвращает все столбцы, лучше указывать явно нужныеWHEREфильтрует строки до группировки,HAVING— послеINNER JOINвозвращает только строки с парой в обеих таблицахLEFT JOIN+WHERE right.id IS NULL— паттерн поиска «битых» FKUPDATEбезWHEREобновит все строки таблицы — опасно- Агрегаты (
COUNT,SUM,AVG) нельзя использовать вWHERE, только вHAVING DISTINCTубирает дубликаты из результата- SQL нечувствителен к регистру ключевых слов:
select=SELECT
Шпаргалка по SQL — все операторы одним экраном
Выборка данных
SELECT * FROM country; -- все столбцы
SELECT id, name FROM country; -- конкретные столбцы
SELECT name AS city_name FROM city; -- псевдоним столбца
SELECT co.name, ci.name FROM city AS ci -- псевдоним таблицы
JOIN country AS co ON ci.country_id = co.id;
Фильтрация — операторы сравнения
SELECT name FROM city WHERE rating > 3;
SELECT name FROM city WHERE name != 'Berlin' AND name != 'Madrid';
Текстовые операторы
-- Начинается на "P" или заканчивается на "s"
SELECT name FROM city WHERE name LIKE 'P%' OR name LIKE '%s';
-- Любая буква, затем "ublin" (например, Dublin)
SELECT name FROM city WHERE name LIKE '_ublin';
-- Ирландия или Польша (по country_id)
SELECT name FROM city WHERE country_id IN (5, 9);
Другие операторы
-- Население от 500 тыс до 5 млн
SELECT name FROM city WHERE population BETWEEN 500000 AND 5000000;
-- Рейтинг не NULL
SELECT name FROM city WHERE rating IS NOT NULL;
-- Страны с id 1, 4, 7, 8
SELECT name FROM city WHERE country_id IN (1, 4, 7, 8);
Сортировка
SELECT name FROM city ORDER BY rating ASC; -- по возрастанию
SELECT name FROM city ORDER BY rating DESC; -- по убыванию
INNER JOIN
Возвращает только строки, у которых есть совпадения в обеих таблицах.
SELECT city.name, country.name
FROM city
[INNER] JOIN country ON city.country_id = country.id;
| CITY | COUNTRY | ||
|---|---|---|---|
| Paris | 1 | 1 | France |
| Berlin | 2 | 2 | Germany |
| Warsaw | 4 | — | — |
Warsaw не попадает — нет совпадения в country
LEFT JOIN
Возвращает все строки левой таблицы + совпавшие из правой. Если совпадения нет — NULL.
SELECT city.name, country.name
FROM city
LEFT JOIN country ON city.country_id = country.id;
| city.name | country.name |
|---|---|
| Paris | France |
| Berlin | Germany |
| Warsaw | NULL |
RIGHT JOIN
Возвращает все строки правой таблицы + совпавшие из левой. Если совпадения нет — NULL.
SELECT city.name, country.name
FROM city
RIGHT JOIN country ON city.country_id = country.id;
| city.name | country.name |
|---|---|
| Paris | France |
| Berlin | Germany |
| NULL | Iceland |
FULL JOIN
Возвращает все строки обеих таблиц. Где нет совпадения — NULL.
SELECT city.name, country.name
FROM city
FULL [OUTER] JOIN country ON city.country_id = country.id;
| city.name | country.name |
|---|---|
| Paris | France |
| Berlin | Germany |
| Warsaw | NULL |
| NULL | Iceland |
CROSS JOIN
Возвращает все возможные комбинации строк двух таблиц (декартово произведение).
SELECT city.name, country.name
FROM city CROSS JOIN country;
-- Альтернативный синтаксис
SELECT city.name, country.name FROM city, country;
NATURAL JOIN
Объединяет таблицы автоматически по столбцам с одинаковым именем.
SELECT city.name, country.name
FROM city
NATURAL JOIN country;
В примере использовались столбцы
country_id,city.name,country.name.
Использовать NATURAL JOIN в реальных проектах не рекомендуется — хрупко при изменении схемы.
Домашнее задание
Выполнить 5 заданий на sql-academy.org + написать SQL с JOIN на «Пиццаеде» в DB Lab. Результат — скриншот + в чат марафона.