Оригинал: Stop Writing Bad SQL: Try These Clever Tricks!

Перевод для канала Мы ж программист

Если вы когда-нибудь писали SQL-запрос и чувствовали, что просто угадываете, как его делается, вы не одиноки. Неважно, начинающий ли вы разработчик или опытный мастер бэкенда, скорее всего, в какой-то момент вам приходилось писать плохой SQL.

Но вот в чем дело: плохой SQL – это не просто неопрятный код. Он может замедлить работу всего приложения, запутать ваших коллег и даже открыть двери для уязвимостей в системе безопасности. 😱

Поэтому в этой статье мы расскажем о некоторых хитроумных трюках с SQL, которые помогут превратить ваши запросы из «не очень» в шедевр. Мы рассмотрим производительность, читаемость, удобство сопровождения и даже подсветим несколько проблем, которые большинство разработчиков упускают из виду.

Так что берите свои ☕ и давайте поднимем уровень вашей игры в SQL!


Как выглядит «плохой SQL»?

Прежде чем мы погрузимся в исправления, давайте определим, как на самом деле выглядит плохой SQL. Вот несколько примеров:

  • Неоптимизированные SELECT, которые получают больше данных, чем нужно
  • Отсутствующие индексы, приводящие к полному сканированию таблицы
  • Жестко закодированные значения, которые делают запросы негибкими
  • Плохая логика JOIN, которая приводит к сбоям при работе с большими наборами данных
  • Непоследовательное именование, вызывающее путаницу
  • Отсутствие комментариев или форматирования

Звучит знакомо? Не волнуйтесь – мы собираемся все это исправить!


1. Прекратите использовать SELECT * – будьте конкретны!

Начнем с кардинального греха SQL:

SQL
SELECT * FROM users;

Несмотря на кажущуюся безобидность, этот запрос может стать катастрофой производительности, особенно если ваша таблица имеет дюжину столбцов (или больше).

Почему это плохо:

  • Извлекает ненужные данные
  • Усложняет отслеживание того, что действительно используется.
  • Ломает код при изменении схемы.

Лучший способ:

SQL
SELECT id, name, email FROM users;

Профессиональный совет: Конкретность также помогает вашим инструментам ORM работать лучше и сокращает время передачи данных.


2. Используйте псевдонимы для упрощения и уточнения

Псевдонимы облегчают чтение запросов и помогают при работе с несколькими соединениями.

Плохо:

SQL
SELECT orders.id, customers.name, orders.date 
FROM orders 
JOIN customers ON orders.customer_id = customers.id;

Лучше:

SQL
SELECT o.id, c.name, o.date 
FROM orders o
JOIN customers c ON o.customer_id = c.id;

Коротко, красиво и читабельно!


3. Индексы: Ваше секретное оружие для повышения скорости

Индексы – это как оглавление для вашей базы данных. Без них SQL должен считывать каждую строку (полное сканирование таблицы).

Как использовать индексы с умом:

  • Индексируйте часто запрашиваемые столбцы (WHERE, JOIN, ORDER BY).
  • Избегайте чрезмерного индексирования – каждый индекс увеличивает нагрузку на запись.
  • Используйте составные индексы для поиска по нескольким столбцам
SQL
CREATE INDEX idx_users_email ON users(email);

Профессиональный совет: Используйте EXPLAIN, чтобы понять, использует ли запрос индексы или нет.


4. Нормализуйте (но не переусердствуйте!)

Нормализация базы данных помогает устранить избыточность, но чрезмерная нормализация может привести к чрезмерному количеству соединений.

Лучшая практика:

  • Используйте 3-ю нормальную форму (3NF) в качестве базового уровня.
  • Отказывайтесь от нормализации только тогда, когда это оправдано производительностью

Плохо:

SQL
SELECT * 
FROM orders 
JOIN customers ON orders.customer_id = customers.id 
JOIN addresses ON customers.address_id = addresses.id 
JOIN regions ON addresses.region_id = regions.id;

Лучше:

Иногда хранение часто используемой информации (например, названия региона) в таблице заказов помогает избежать сложных объединений в запросах, выполняемых в режиме реального времени.


5. Агрегатные функции, выполненные правильно

Такие функции, как COUNT(), SUM() и AVG(), являются мощными – но часто используются неправильно.

Избегайте этого:

SQL
SELECT COUNT(*) FROM orders WHERE status = 'shipped';

Хотя этот способ работает, он может быть неэффективным для больших таблиц.

Вместо этого рассмотрите следующие варианты:

  • Индексирование столбца status
  • Создание материализованного представления (materialized view), если запрос частый

6. Используйте CTE для удобочитаемости (используем WITH)

Common Table Expressions (CTE) разбивают сложные запросы на понятные блоки. Думайте о них как о временных именованных подзапросах.

SQL
WITH recent_orders AS (
  SELECT * FROM orders WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
)
SELECT customer_id, COUNT(*) 
FROM recent_orders 
GROUP BY customer_id;

Чище, проще для отладки и полностью модульно!


7. Очищайте вводные данные, всегда!

SQL-инъекции по-прежнему остаются одним из самых опасных эксплойтов.

Никогда не делайте так:

Python
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")

Лучше так:

Python
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))

Используйте подготовленные запросы (prepared statements) или ORM, которые очищают данные автоматически.


8. Анализируйте и оптимизируйте с помощью EXPLAIN и ANALYZE

Это ваши рентгеновские очки, позволяющие понять, что на самом деле делает SQL под капотом.

SQL
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';

Ищите:

  • Последовательное сканирование
  • Использование индексов
  • Типы соединений (join)

Используйте инструменты pgAdmin или MySQL Workbench для визуального анализа.


9. Делайте запросы предсказуемыми (избегайте случайностей)

Используете ORDER BY RAND(), чтобы перетасовать строки?

SQL
SELECT * FROM users ORDER BY RAND() LIMIT 1;

Это не масштабируется и может привести к падению сервера при работе с большими таблицами.

Альтернативы:

  • Использовать случайное смещение (если таблица маленькая).
  • Предварительно вычислять случайные выборки и хранить их
  • Использовать TABLESAMPLE (в PostgreSQL)

10. Ограничьте то, что нужно

Не стоит получать 10 000 строк только для того, чтобы отобразить 10.

Плохо:

SQL
SELECT * FROM logs;

Лучше:

SQL
SELECT * FROM logs ORDER BY created_at DESC LIMIT 10;

Для получения предсказуемых результатов всегда используйте LIMIT в паре с ORDER BY.


11. Пакетные вставки и обновления больших объемов

Пытаетесь вставить 100 000 строк за один раз?

Скорее всего, это вызовет проблемы с памятью или таймаут.

Более разумная стратегия:

  • Разбейте на партии (например, по 1000 строк).
  • Используйте транзакции для обеспечения атомарности
  • Используйте инструменты массовой вставки (COPY, LOAD DATA и т. д.).

12. Не злоупотребляйте подзапросами

Подзапросы внутри WHERE или SELECT могут стать узкими местами в производительности.

Вместо:

SQL
SELECT name FROM customers 
WHERE id IN (SELECT customer_id FROM orders WHERE total > 500);

Попробуйте:

SQL
SELECT DISTINCT c.name 
FROM customers c
JOIN orders o ON c.id = o.customer_id 
WHERE o.total > 500;

JOINы в общем случае более оптимизированы, чем подзапросы.


13. Поддерживайте чистоту кода с помощью форматирования

Вы бы не стали писать на Python нечитаемый код – так не делайте этого с SQL!

Используйте такие инструменты, как:

Форматируйте запросы так:

SQL
SELECT 
  c.name, 
  COUNT(o.id) AS total_orders
FROM 
  customers c
JOIN 
  orders o ON c.id = o.customer_id
GROUP BY 
  c.name
ORDER BY 
  total_orders DESC;

Выглядит красиво, правда?


Дополнительные советы: Мониторинг и настройка со временем

SQL – это не «написал один раз и забыл». По мере роста ваших данных должны расти и ваши усилия по оптимизации.

  • Используйте журналы запросов для поиска медленных запросов
  • Следите за показателями производительности
  • Периодически проводите вакуум/анализ (PostgreSQL).
  • При необходимости архивируйте старые данные.

SQL дзен – это постоянная, внимательная настройка.


Заключительные мысли

Написать хороший SQL – это не значит заучить тысячу функций, это значит ясно мыслить, понимать свои данные и делать разумный выбор.

Вооружившись этими умными приемами, вы сможете избежать типичных ошибок, которые подстерегают многие проекты, и вместо этого создать чистый, эффективный и масштабируемый SQL.

В следующий раз, когда вы будете писать запрос, помните: вы не просто общаетесь с базой данных – вы определяете будущую производительность всего вашего приложения.

А теперь отправляйтесь на рефакторинг того уродливого запроса, которого вы так долго избегали!