Примеры запросов в SQL
1. Базовые SELECT-запросы
1.1. Выбор всех столбцов из таблицы
SELECT * FROM employees;
1.2. Выбор конкретных столбцов
SELECT first_name, last_name, email FROM employees;
1.3. Выбор с псевдонимами столбцов
SELECT first_name AS name, department_id AS dept FROM employees;
1.4. Выбор уникальных значений (DISTINCT)
SELECT DISTINCT department_id FROM employees;
1.5. Выбор с ограничением количества строк (LIMIT / TOP / FETCH)
-- PostgreSQL, MySQL, SQLite
SELECT * FROM employees LIMIT 10;
-- MS SQL Server (до 2012)
SELECT TOP 10 * FROM employees;
-- MS SQL Server (2012+), стандартный SQL:2008
SELECT * FROM employees
ORDER BY employee_id
FETCH FIRST 10 ROWS ONLY;
2. Фильтрация: WHERE
2.1. Простое условие
SELECT * FROM employees WHERE salary > 50000;
2.2. Составное условие
SELECT * FROM employees
WHERE salary BETWEEN 40000 AND 70000
AND department_id = 3;
2.3. Поиск по шаблону (LIKE)
SELECT * FROM employees
WHERE last_name LIKE 'Sm%'; -- начинается на Sm
-- Регистронезависимый поиск (PostgreSQL)
SELECT * FROM employees
WHERE last_name ILIKE 'sm%';
-- MySQL: регистронезависимый по умолчанию (если collation _ci)
-- MS SQL: чувствителен к регистру — используй UPPER() или COLLATE
SELECT * FROM employees
WHERE UPPER(last_name) LIKE 'SM%';
2.4. Проверка на NULL
SELECT * FROM employees WHERE manager_id IS NULL;
SELECT * FROM employees WHERE manager_id IS NOT NULL;
2.5. Множественное условие (IN)
SELECT * FROM employees
WHERE department_id IN (1, 3, 5);
2.6. Отрицание (NOT)
SELECT * FROM employees
WHERE NOT (salary > 60000 AND department_id = 2);
-- Эквивалент:
SELECT * FROM employees
WHERE salary <= 60000 OR department_id <> 2;
3. Сортировка: ORDER BY
3.1. Сортировка по одному полю
SELECT * FROM employees
ORDER BY last_name ASC;
3.2. Сортировка по нескольким полям
SELECT * FROM employees
ORDER BY department_id ASC, salary DESC;
3.3. Сортировка с NULL в нужной позиции
-- PostgreSQL: NULLS LAST / NULLS FIRST
SELECT * FROM employees
ORDER BY commission_pct DESC NULLS LAST;
-- MS SQL: используйте CASE
SELECT * FROM employees
ORDER BY
CASE WHEN commission_pct IS NULL THEN 1 ELSE 0 END,
commission_pct DESC;
4. Агрегация и группировка: GROUP BY, HAVING
4.1. Простая агрегация
SELECT COUNT(*) AS total_employees FROM employees;
SELECT AVG(salary) AS avg_salary FROM employees;
SELECT MIN(salary), MAX(salary), SUM(salary) FROM employees;
4.2. Группировка по столбцу
SELECT department_id, COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;
4.3. Группировка с фильтрацией по агрегатам (HAVING)
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 50000;
4.4. Агрегация с COALESCE для NULL
SELECT department_id,
COUNT(*) AS total,
AVG(COALESCE(commission_pct, 0)) AS avg_commission
FROM employees
GROUP BY department_id;
5. Соединения (JOIN)
5.1. INNER JOIN
SELECT e.first_name, e.last_name, d.department_name
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id;
5.2. LEFT JOIN (все строки из левой таблицы)
SELECT e.first_name, d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;
-- Покажет всех сотрудников, даже без департамента (department_name = NULL)
5.3. RIGHT JOIN (аналогично, но сохраняет все из правой)
SELECT e.first_name, d.department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;
-- Покажет все департаменты, даже без сотрудников
5.4. FULL OUTER JOIN (все строки из обеих таблиц)
-- Поддерживается в PostgreSQL, MS SQL
SELECT e.employee_id, d.department_id
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.department_id;
-- В MySQL: эмулируется через UNION
SELECT e.employee_id, d.department_id
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
UNION
SELECT e.employee_id, d.department_id
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id
WHERE e.employee_id IS NULL;
5.5. Самосоединение (SELF JOIN)
SELECT e1.first_name AS employee,
e2.first_name AS manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id;
6. Подзапросы
6.1. Скалярный подзапрос (возвращает одно значение)
SELECT first_name, last_name,
(SELECT department_name
FROM departments d
WHERE d.department_id = e.department_id) AS dept_name
FROM employees e;
✅ Работает, если связь
1:1или гарантируется единственность.
⚠️ Может быть медленнееJOIN, особенно без индекса.
6.2. Подзапрос в WHERE — поиск по списку (IN)
SELECT * FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location_id = 1700
);
6.3. Коррелированный подзапрос (зависит от внешнего запроса)
SELECT e1.first_name, e1.last_name, e1.salary
FROM employees e1
WHERE e1.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
);
-- Сотрудники, зарабатывающие выше среднего в своём департаменте
6.4. EXISTS — проверка наличия
SELECT d.department_name
FROM departments d
WHERE EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.department_id
AND e.salary > 10000
);
-- Департаменты, где есть хотя бы один сотрудник с ЗП > 10 000
✅
EXISTSчасто эффективнееIN, особенно при работе сNULL.
6.5. Подзапрос в FROM (производная таблица / inline view)
SELECT dept_name, avg_sal
FROM (
SELECT d.department_name AS dept_name,
AVG(e.salary) AS avg_sal
FROM employees e
JOIN departments d ON e.department_id = d.department_id
GROUP BY d.department_id, d.department_name
) sub
WHERE avg_sal > 50000;
7. Общие табличные выражения (WITH, CTE)
7.1. Простой CTE
WITH high_salary AS (
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > 8000
)
SELECT * FROM high_salary
ORDER BY salary DESC;
7.2. Множественные CTE
WITH
dept_avg AS (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
),
above_avg AS (
SELECT e.first_name, e.last_name, e.salary, da.avg_sal
FROM employees e
JOIN dept_avg da ON e.department_id = da.department_id
WHERE e.salary > da.avg_sal
)
SELECT * FROM above_avg;
7.3. Рекурсивный CTE (иерархия: например, оргструктура)
WITH RECURSIVE emp_hierarchy AS (
-- Якорь: топ-менеджеры (без руководителя)
SELECT employee_id, first_name, last_name, manager_id, 0 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Рекурсия: подчинённые
SELECT e.employee_id, e.first_name, e.last_name, e.manager_id, eh.level + 1
FROM employees e
INNER JOIN emp_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT REPEAT(' ', level) || first_name || ' ' || last_name AS tree_view
FROM emp_hierarchy
ORDER BY level, employee_id;
✅ Поддерживается в PostgreSQL, MS SQL Server, SQLite (3.8.3+), Oracle.
❌ MySQL поддерживает с 8.0.
8. Оконные функции (OVER)
8.1. Ранжирование
SELECT
first_name, last_name, department_id, salary,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS drnk
FROM employees;
ROW_NUMBER()— уникальный номер (1,2,3…)RANK()— одинаковые значения → одинаковый ранг, следующий пропускается (1,1,3)DENSE_RANK()— одинаковые значения → одинаковый ранг, без пропусков (1,1,2)
8.2. Накопительная сумма
SELECT
hire_date, salary,
SUM(salary) OVER (ORDER BY hire_date ROWS UNBOUNDED PRECEDING) AS running_total
FROM employees
ORDER BY hire_date;
8.3. Сравнение с предыдущей строкой (LAG/LEAD)
SELECT
first_name, hire_date, salary,
LAG(salary, 1) OVER (ORDER BY hire_date) AS prev_salary,
salary - LAG(salary, 1) OVER (ORDER BY hire_date) AS diff
FROM employees
ORDER BY hire_date;
8.4. Доля в группе (PERCENT_RANK, CUME_DIST, % от суммы)
SELECT
department_id,
first_name,
salary,
ROUND(
100.0 * salary / SUM(salary) OVER (PARTITION BY department_id),
2
) AS pct_of_dept_total
FROM employees;
9. Изменение данных (DML)
9.1. Вставка одной строки
INSERT INTO employees (
employee_id, first_name, last_name, email, hire_date, job_id, salary, department_id
) VALUES (
999, 'Алексей', 'Петров', 'apetrov@example.com', '2025-11-13', 'IT_PROG', 75000, 60
);
9.2. Вставка из другой таблицы
INSERT INTO archive_employees
SELECT * FROM employees
WHERE hire_date < '2020-01-01';
9.3. Обновление с JOIN (не ANSI, но часто используется)
-- PostgreSQL, MySQL
UPDATE employees e
SET salary = salary * 1.1
FROM departments d
WHERE e.department_id = d.department_id
AND d.department_name = 'IT';
-- MS SQL Server
UPDATE e
SET salary = salary * 1.1
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.department_name = 'IT';
-- ANSI-совместимый (через подзапрос)
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = (
SELECT department_id
FROM departments
WHERE department_name = 'IT'
);
9.4. Условное обновление (CASE)
UPDATE employees
SET salary = CASE
WHEN job_id = 'MANAGER' THEN salary * 1.15
WHEN job_id = 'SA_REP' THEN salary * 1.10
ELSE salary
END
WHERE department_id = 80;
9.5. Удаление с подзапросом
DELETE FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location_id = 1800
);
9.6. Умное обновление/вставка (UPSERT / MERGE)
PostgreSQL (ON CONFLICT)
INSERT INTO employees (employee_id, first_name, salary)
VALUES (999, 'Иван', 60000)
ON CONFLICT (employee_id)
DO UPDATE SET
first_name = EXCLUDED.first_name,
salary = EXCLUDED.salary;
MySQL (ON DUPLICATE KEY UPDATE)
INSERT INTO employees (employee_id, first_name, salary)
VALUES (999, 'Иван', 60000)
ON DUPLICATE KEY UPDATE
first_name = VALUES(first_name),
salary = VALUES(salary);
MS SQL / Oracle (MERGE)
MERGE INTO employees AS target
USING (SELECT 999 AS id, 'Иван' AS name, 60000 AS sal) AS source
ON target.employee_id = source.id
WHEN MATCHED THEN
UPDATE SET first_name = source.name, salary = source.sal
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, salary)
VALUES (source.id, source.name, source.sal);
10. Работа с датами и временем
10.1. Текущая дата/время
-- Стандартный SQL (совместимо с большинством СУБД)
SELECT CURRENT_DATE; -- дата
SELECT CURRENT_TIMESTAMP; -- дата + время + часовой пояс (если поддерживается)
-- PostgreSQL
SELECT NOW(); -- = CURRENT_TIMESTAMP
SELECT CLOCK_TIMESTAMP(); -- точное время выполнения (даже внутри одного запроса)
-- MySQL
SELECT CURDATE();
SELECT NOW(); -- DATETIME (без часового пояса)
SELECT UTC_TIMESTAMP();
-- MS SQL Server
SELECT GETDATE(); -- DATETIME (локальное)
SELECT GETUTCDATE();
SELECT SYSDATETIME(); -- более высокая точность (до 100 нс)
10.2. Извлечение компонентов (EXTRACT, DATE_PART)
-- Стандарт SQL / PostgreSQL
SELECT
EXTRACT(YEAR FROM hire_date) AS yr,
EXTRACT(MONTH FROM hire_date) AS mth,
EXTRACT(DAY FROM hire_date) AS day,
EXTRACT(DOW FROM hire_date) AS weekday -- 0=вс (PostgreSQL), 1=пн (MS SQL)
FROM employees;
-- MySQL
SELECT
YEAR(hire_date),
MONTH(hire_date),
DAY(hire_date),
WEEKDAY(hire_date); -- 0=пн, 6=вс
-- MS SQL Server
SELECT
YEAR(hire_date),
MONTH(hire_date),
DAY(hire_date),
DATEPART(WEEKDAY, hire_date); -- зависит от SET DATEFIRST
10.3. Арифметика дат
-- PostgreSQL
SELECT hire_date + INTERVAL '1 year' AS next_year,
hire_date + INTERVAL '3 months 5 days' AS adjusted
FROM employees;
-- MySQL
SELECT
DATE_ADD(hire_date, INTERVAL 1 YEAR) AS next_year,
hire_date + INTERVAL 3 MONTH + INTERVAL 5 DAY AS adjusted
FROM employees;
-- MS SQL Server
SELECT
DATEADD(YEAR, 1, hire_date) AS next_year,
DATEADD(DAY, 5, DATEADD(MONTH, 3, hire_date)) AS adjusted
FROM employees;
10.4. Группировка по периодам
-- Месяцы (PostgreSQL)
SELECT
DATE_TRUNC('month', hire_date)::DATE AS month_start,
COUNT(*) AS hires
FROM employees
GROUP BY month_start
ORDER BY month_start;
-- MySQL
SELECT
DATE_FORMAT(hire_date, '%Y-%m-01') AS month_start,
COUNT(*)
FROM employees
GROUP BY month_start
ORDER BY month_start;
-- MS SQL Server
SELECT
DATEFROMPARTS(YEAR(hire_date), MONTH(hire_date), 1) AS month_start,
COUNT(*)
FROM employees
GROUP BY YEAR(hire_date), MONTH(hire_date)
ORDER BY month_start;
11. Работа со строками
11.1. Конкатенация
-- Стандарт (SQL-92)
SELECT first_name || ' ' || last_name AS full_name FROM employees;
-- MySQL, MS SQL (<2012): CONCAT()
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM employees;
-- MS SQL (2012+): CONCAT() + ISNULL/COALESCE для NULL-safe
SELECT CONCAT(first_name, ' ', COALESCE(middle_name, ''), ' ', last_name)
FROM employees;
-- PostgreSQL: CONCAT() и CONCAT_WS()
SELECT CONCAT_WS(' ', first_name, middle_name, last_name) FROM employees;
11.2. Регулярные выражения
PostgreSQL
-- Совпадение
SELECT * FROM employees
WHERE email ~* '^[a-z0-9._%+-]+@example\.com$';
-- Замена
SELECT REGEXP_REPLACE(phone_number, '\D', '', 'g') AS digits_only
FROM employees;
MySQL (8.0+)
SELECT * FROM employees
WHERE email REGEXP '^[a-z0-9._%+-]+@example\\.com$';
SELECT REGEXP_REPLACE(phone_number, '[^0-9]', '') AS digits_only
FROM employees;
MS SQL Server — без встроенных regexp; используйте LIKE или CLR-функции. Пример с LIKE:
-- Простой паттерн: email с @example.com
SELECT * FROM employees
WHERE email LIKE '%@example.com';
-- Для сложных случаев — внешняя обработка или CLR.
11.3. Разбор строк (разделение по символу)
PostgreSQL (string_to_array, unnest)
SELECT id, UNNEST(STRING_TO_ARRAY(tags, ',')) AS tag
FROM products;
MySQL (8.0+, JSON_TABLE или REGEXP_SUBSTR)
-- Через генерацию последовательности и REGEXP_SUBSTR
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 10
)
SELECT
p.id,
TRIM(REGEXP_SUBSTR(p.tags, '[^,]+', 1, n.n)) AS tag
FROM products p
JOIN nums n ON n.n <= 1 + (LENGTH(p.tags) - LENGTH(REPLACE(p.tags, ',', '')))
WHERE tag IS NOT NULL AND tag <> '';
MS SQL Server (2016+, STRING_SPLIT)
SELECT p.id, TRIM(value) AS tag
FROM products p
CROSS APPLY STRING_SPLIT(p.tags, ',')
WHERE TRIM(value) <> '';
12. Работа с JSON
12.1. PostgreSQL (JSONB — рекомендуется)
-- Вставка JSON-объекта
INSERT INTO logs (event_data)
VALUES ('{"user_id": 101, "action": "login", "params": {"ip": "192.168.1.5"}}'::JSONB);
-- Извлечение полей
SELECT
event_data->>'user_id' AS user_id,
event_data->'params'->>'ip' AS ip
FROM logs;
-- Фильтрация по JSON-ключу
SELECT * FROM logs
WHERE event_data @> '{"action": "login"}';
-- Индексация (ускоряет поиск по JSON)
CREATE INDEX idx_logs_action ON logs USING GIN ((event_data->'action'));
12.2. MySQL (5.7+, JSON тип)
-- Извлечение
SELECT
JSON_UNQUOTE(JSON_EXTRACT(event_data, '$.user_id')) AS user_id,
JSON_UNQUOTE(JSON_EXTRACT(event_data, '$.params.ip')) AS ip
FROM logs;
-- Упрощённый синтаксис (MySQL 8.0+)
SELECT
event_data->>'$.user_id' AS user_id,
event_data->>'$.params.ip' AS ip
FROM logs;
-- Поиск по JSON-ключу
SELECT * FROM logs
WHERE JSON_CONTAINS_PATH(event_data, 'one', '$.action')
AND JSON_UNQUOTE(JSON_EXTRACT(event_data, '$.action')) = 'login';
-- Индекс (функциональный)
ALTER TABLE logs
ADD COLUMN action VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(event_data, '$.action')));
CREATE INDEX idx_logs_action ON logs(action);
12.3. MS SQL Server (2016+, NVARCHAR + JSON_* функции)
-- Проверка
SELECT * FROM logs
WHERE ISJSON(event_data) = 1;
-- Извлечение
SELECT
JSON_VALUE(event_data, '$.user_id') AS user_id,
JSON_VALUE(event_data, '$.params.ip') AS ip
FROM logs;
-- Фильтрация
SELECT * FROM logs
WHERE JSON_VALUE(event_data, '$.action') = 'login';
-- Индекс — только по вычисляемому столбцу
ALTER TABLE logs
ADD action AS JSON_VALUE(event_data, '$.action');
CREATE INDEX idx_logs_action ON logs(action);
13. Поворот таблиц (Pivot / Cross-tabulation)
13.1. Условная агрегация (универсально)
SELECT
department_id,
COUNT(*) FILTER (WHERE gender = 'M') AS male_count,
COUNT(*) FILTER (WHERE gender = 'F') AS female_count,
AVG(salary) FILTER (WHERE job_id = 'IT_PROG') AS avg_it_salary
FROM employees
GROUP BY department_id;
✅ Поддерживается в PostgreSQL (начиная с 9.4), MS SQL Server (через
CASE), SQLite.
MySQL: используйтеCASE WHEN.
Альтернатива для MySQL / MS SQL:
SELECT
department_id,
SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS male_count,
SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS female_count,
AVG(CASE WHEN job_id = 'IT_PROG' THEN salary END) AS avg_it_salary
FROM employees
GROUP BY department_id;
13.2. PIVOT (MS SQL Server)
SELECT department_id, [IT_PROG], [SA_REP], [ST_CLERK]
FROM (
SELECT department_id, job_id, salary
FROM employees
) src
PIVOT (
AVG(salary)
FOR job_id IN ([IT_PROG], [SA_REP], [ST_CLERK])
) pvt;
13.3. Динамический PIVOT (MS SQL — через динамический SQL)
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX);
SELECT @cols = STRING_AGG(QUOTENAME(job_id), ', ')
FROM (SELECT DISTINCT job_id FROM employees) AS jobs;
SET @query = '
SELECT department_id, ' + @cols + '
FROM (
SELECT department_id, job_id, salary
FROM employees
) src
PIVOT (
AVG(salary)
FOR job_id IN (' + @cols + ')
) pvt;';
EXEC sp_executesql @query;
14. Генерация данных / последовательностей
14.1. Генерация дней за период (PostgreSQL)
SELECT day::DATE
FROM GENERATE_SERIES(
'2025-01-01'::DATE,
'2025-12-31'::DATE,
'1 day'
) AS day;
14.2. Генерация последовательности (рекурсивный CTE, кроссплатформенно)
WITH RECURSIVE nums AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM nums WHERE n < 365
)
SELECT n FROM nums;
14.3. Генерация временных слотов (каждые 15 минут)
WITH RECURSIVE slots AS (
SELECT '09:00'::TIME AS slot
UNION ALL
SELECT (slot + INTERVAL '15 minutes')::TIME
FROM slots
WHERE slot < '18:00'
)
SELECT slot FROM slots;
15. Практические отчётные шаблоны
15.1. TOP-N в каждой группе (например: ТОП-3 з/п в отделе)
WITH ranked AS (
SELECT
first_name, last_name, department_id, salary,
ROW_NUMBER() OVER (
PARTITION BY department_id
ORDER BY salary DESC
) AS rn
FROM employees
)
SELECT * FROM ranked
WHERE rn <= 3;
15.2. Скользящее среднее (3-дневное)
SELECT
hire_date,
COUNT(*) AS hires_today,
AVG(COUNT(*)) OVER (
ORDER BY hire_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS moving_avg_3d
FROM employees
GROUP BY hire_date
ORDER BY hire_date;
15.3. Retention (повторное действие в течение N дней)
Пример: пользователи, сделавшие повторную покупку в течение 7 дней после первой.
WITH first_orders AS (
SELECT
customer_id,
MIN(order_date) AS first_date
FROM orders
GROUP BY customer_id
),
repeat_customers AS (
SELECT DISTINCT o.customer_id
FROM orders o
JOIN first_orders f ON o.customer_id = f.customer_id
WHERE o.order_date > f.first_date
AND o.order_date <= f.first_date + INTERVAL '7 days'
)
SELECT
COUNT(DISTINCT f.customer_id) AS total_customers,
COUNT(DISTINCT r.customer_id) AS retained,
ROUND(100.0 * COUNT(r.customer_id) / COUNT(f.customer_id), 2) AS retention_pct
FROM first_orders f
LEFT JOIN repeat_customers r ON f.customer_id = r.customer_id;
16. Анализ плана выполнения: EXPLAIN
16.1. Базовый план
EXPLAIN SELECT * FROM employees WHERE department_id = 60;
-- Выводит дерево операций (Seq Scan, Index Scan и т.д.)
EXPLAIN ANALYZE SELECT * FROM employees WHERE department_id = 60;
-- Выполняет запрос и показывает реальное время, число строк и т.п.
16.2. Интерпретация ключевых узлов
| Узел | Что означает | На что смотреть |
|---|---|---|
Seq Scan | Полное сканирование таблицы | Избегать на больших таблицах без LIMIT |
Index Scan / Index Only Scan | Использование индекса | Index Only — лучше (нет обращения к таблице) |
Bitmap Index Scan + Bitmap Heap Scan | Для множественных условий | Эффективно при среднем селективном фильтре |
Hash Join / Nested Loop / Merge Join | Алгоритм соединения | Nested Loop — хорошо при малом правом множестве |
Sort | Явная сортировка (без индекса) | Может быть дорого — добавьте индекс под ORDER BY |
16.3. Советы по оптимизации
- ✅ Добавляйте индексы на
WHERE,JOIN,ORDER BY. - ✅ Избегайте
SELECT *в production-запросах — явно указывайте поля. - ✅ Не оборачивайте колонку в функцию в
WHERE:
❌WHERE UPPER(name) = 'IVAN'→ индекс не используется
✅WHERE name = 'Ivan'(и используйте регистронезависимый collation или функциональный индекс). - ✅ Для пагинации на больших данных — курсоры (
DECLARE CURSOR) или ключевая пагинация (WHERE id > last_seen_id), а неOFFSET.