← Назад к вопросам

Какие условия нельзя делать в where?

2.2 Middle🔥 111 комментариев
#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Условия, которые нельзя делать в WHERE (для оптимизации)

Есть определённые типы условий в WHERE, которые предотвращают использование индексов и приводят к полному сканированию таблицы (Table Scan). Это критично для производительности больших таблиц.

1. Функции над столбцами (Function Calls)

Это главный враг индексов. Если применяешь функцию к столбцу, индекс больше не работает.

-- ❌ ПЛОХО: функция над столбцом (Full Table Scan)
SELECT * FROM users WHERE UPPER(name) = "JOHN";
SELECT * FROM users WHERE EXTRACT(YEAR FROM created_at) = 2024;
SELECT * FROM users WHERE LENGTH(email) > 10;
SELECT * FROM users WHERE SUBSTRING(phone, 1, 3) = "555";
SELECT * FROM products WHERE price * 1.1 > 100;  -- Вычисление в WHERE

-- ✅ ХОРОШО: функция на константе или переписываем условие
SELECT * FROM users WHERE name = "john";  -- Как есть, без функции

SELECT * FROM users
WHERE created_at >= "2024-01-01" AND created_at < "2025-01-01";

SELECT * FROM users WHERE email LIKE "%@%._%" AND LENGTH(email) > 10;

SELECT * FROM users WHERE phone LIKE "555%";

SELECT * FROM products WHERE price > 90.9;  -- 100 / 1.1

Почему это плохо: БД должна выполнить функцию для каждой строки, прежде чем проверить условие. Индекс хранит значения столбцов, но не результаты функций.

2. Операторы LIKE с wildcards в начале

Поиск по шаблону с % в начале не использует индекс.

-- ❌ ПЛОХО: % в начале (Full Table Scan)
SELECT * FROM users WHERE email LIKE "%gmail.com";
SELECT * FROM users WHERE name LIKE "%john%";
SELECT * FROM products WHERE description LIKE "%red%";

-- ✅ ХОРОШО: % в конце (использует индекс)
SELECT * FROM users WHERE email LIKE "john%";
SELECT * FROM users WHERE email LIKE "john@%";

-- ✅ ЛУЧШЕ: точный поиск или другой подход
SELECT * FROM users WHERE email LIKE "john@gmail.com";
-- Или для поиска по части, лучше использовать FTS (Full Text Search)
SELECT * FROM users WHERE to_tsvector(description) @@ to_tsquery("red");

Исключение: Некоторые БД (как PostgreSQL) поддерживают специальные индексы для поиска по префиксу.

3. Использование OR с разными столбцами

OR часто приводит к отказу от индекса.

-- ❌ ПЛОХО: OR с несколькими столбцами
SELECT * FROM users WHERE id = 1 OR email = "john@example.com";
SELECT * FROM orders WHERE user_id = 5 OR status = "completed";

-- ✅ ХОРОШО: используй UNION (часто быстрее)
SELECT * FROM users WHERE id = 1
UNION
SELECT * FROM users WHERE email = "john@example.com";

-- ✅ ХОРОШО: если оба столбца в составном индексе
CREATE INDEX idx_users ON users(id, email);
SELECT * FROM users WHERE id = 1 OR email = "john@example.com";

-- ❌ ПЛОХО: OR с разными столбцами разных таблиц
SELECT * FROM orders WHERE user_id = 1 OR product_id = 5;
-- Нужны индексы на обоих столбцах, но БД может выбрать только один

Примечание: Современные оптимизаторы улучшились, но не надейся на это.

4. Неравенства со сложными выражениями

Вычисления в WHERE часто приводят к Full Table Scan.

-- ❌ ПЛОХО: вычисления
SELECT * FROM products WHERE price * quantity > 1000;
SELECT * FROM users WHERE TIMESTAMPDIFF(YEAR, created_at, NOW()) > 5;
SELECT * FROM orders WHERE amount - discount > 100;

-- ✅ ХОРОШО: переносим вычисления на другую сторону
SELECT * FROM products WHERE price > 1000 / quantity;
SELECT * FROM users WHERE created_at < NOW() - INTERVAL 5 YEAR;
SELECT * FROM orders WHERE amount > 100 + discount;

-- ✅ ЛУЧШЕ: использми индекс для результата (вычисленный столбец)
ALTER TABLE products ADD COLUMN total_value GENERATED AS (price * quantity);
CREATE INDEX idx_total_value ON products(total_value);
SELECT * FROM products WHERE total_value > 1000;

5. NOT и отрицания

Отрицание часто предотвращает использование индекса.

-- ❌ ПЛОХО: NOT часто приводит к Full Table Scan
SELECT * FROM users WHERE NOT status = "active";
SELECT * FROM orders WHERE NOT user_id = 5;
SELECT * FROM products WHERE NOT category IN ("electronics", "books");

-- ✅ ХОРОШО: переписываем позитивно
SELECT * FROM users WHERE status != "active";  # Или status <> "active"
SELECT * FROM users WHERE status IN ("inactive", "deleted");
SELECT * FROM orders WHERE user_id != 5;
SELECT * FROM products WHERE category NOT IN ("electronics", "books");

-- ✅ ЛУЧШЕ: используй IS NULL вместо NOT EXISTS
SELECT * FROM users WHERE deleted_at IS NULL;  -- Лучше
SELECT * FROM users WHERE NOT deleted_at IS NOT NULL;  -- Хуже

6. Регулярные выражения (REGEXP, LIKE с паттернами)

Регулярные выражения требуют полного сканирования строк.

-- ❌ ПЛОХО: regex (Full Table Scan)
SELECT * FROM users WHERE email REGEXP "^[a-z]+@gmail\\.com$";
SELECT * FROM products WHERE sku REGEXP "^PROD-[0-9]{4}$";

-- ✅ ХОРОШО: точный поиск
SELECT * FROM users WHERE email LIKE "%@gmail.com";
SELECT * FROM products WHERE sku LIKE "PROD-%";

-- ✅ ЛУЧШЕ: используй индексы правильно
SELECT * FROM users WHERE email LIKE "%.%@gmail.com";

7. NULL сравнения (иногда)

В некоторых БД NULL сравнения не используют индекс.

-- ❌ ПЛОХО: в некоторых БД индекс не используется
SELECT * FROM users WHERE phone = NULL;

-- ✅ ПРАВИЛЬНО: всегда используй IS NULL
SELECT * FROM users WHERE phone IS NULL;
SELECT * FROM users WHERE phone IS NOT NULL;

-- Совет: если часто проверяешь на NULL, создай индекс
CREATE INDEX idx_phone_null ON users(phone) WHERE phone IS NULL;

8. Приведение типов (Type Conversion)

Неявное приведение типов может сломать индекс.

-- ❌ ПЛОХО: сравнение разных типов
SELECT * FROM users WHERE id = "123";  -- id int, сравниваем со string
SELECT * FROM products WHERE price = "99.99";  -- price decimal vs string

-- ✅ ПРАВИЛЬНО: используй правильные типы
SELECT * FROM users WHERE id = 123;
SELECT * FROM products WHERE price = 99.99;

-- ⚠️ Особенно опасно в Python/ORM
# ❌ ПЛОХО
user = User.objects.filter(id="123")  # строка вместо int

# ✅ ХОРОШО
user = User.objects.filter(id=123)  # правильный тип

9. Использование IN с подзапросом (иногда)

Подзапросы в IN могут быть медленными на больших наборах.

-- ❌ ПЛОХО: подзапрос в IN (может быть медленнее)
SELECT * FROM orders WHERE user_id IN (
    SELECT id FROM users WHERE created_at > "2024-01-01"
);

-- ✅ ХОРОШО: используй JOIN
SELECT DISTINCT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.created_at > "2024-01-01";

-- ✅ ХОРОШО: если IN список маленький (< 10 элементов)
SELECT * FROM orders WHERE user_id IN (1, 2, 3, 4, 5);

10. Плохой порядок условий

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

-- ❌ МОЖЕТ быть медленнее: селективное условие в конце
SELECT * FROM orders
WHERE status = "completed" AND amount > 1000;
-- Если "completed" редкий статус, но у amount > 1000 миллионы строк

-- ✅ ЛУЧШЕ: селективное условие первым
SELECT * FROM orders
WHERE amount > 1000 AND status = "completed";
-- Если amount > 1000 встречается реже

-- ⚠️ На самом деле, оптимизатор сам переупорядочит,
-- но зависит от статистики и версии БД

Таблица сравнения

УсловиеИспользует индексПроблемаРешение
column = value✅ ДаНет-
UPPER(column) = value❌ НетФункцияУбрать функцию
column LIKE "%abc%"❌ Нет% в началеИспользовать LIKE "abc%"
column LIKE "abc%"✅ ДаНет-
column IN (SELECT ...)⚠️ ИногдаПодзапросИспользовать JOIN
column NOT IN (...)❌ НетNOTПереписать условие
column != value⚠️ ИногдаНеравенствоЗависит от БД
column IS NULL✅ Да*Специальный индексСоздать индекс
column OR ...❌ НетORИспользовать UNION
CAST(column AS type)❌ НетПриведение типовИспользовать правильный тип

Python ORM примеры

# ❌ ПЛОХО
from sqlalchemy import func

# Функция в WHERE
users = session.query(User).filter(func.upper(User.name) == "JOHN")

# OR множество столбцов
orders = session.query(Order).filter(
    (Order.user_id == 1) | (Order.status == "completed")
)

# ✅ ХОРОШО
# Используй как есть
users = session.query(User).filter(User.name == "John")

# Используй and/or правильно
from sqlalchemy import and_, or_

# Лучше разделить
orders1 = session.query(Order).filter(Order.user_id == 1)
orders2 = session.query(Order).filter(Order.status == "completed")
result = orders1.union(orders2)

# Или используй JOIN
result = session.query(Order).join(User).filter(
    (Order.user_id == User.id) & (User.created_at > "2024-01-01")
)

Профилирование запросов

-- PostgreSQL: EXPLAIN ANALYZE
EXPLAIN ANALYZE
SELECT * FROM orders WHERE UPPER(user_name) = "JOHN";

-- MySQL: EXPLAIN
EXPLAIN
SELECT * FROM orders WHERE UPPER(user_name) = "JOHN";

-- Смотрим на:
-- 1. type: ALL (Full Scan), INDEX, RANGE, REF (индекс используется)
-- 2. rows: сколько строк скканировано
-- 3. Extra: "Using index" (хорошо), "Using where" (может быть медленно)

Best Practices

  1. Никогда не применяй функции к столбцам в WHERE
  2. Используй точный поиск вместо LIKE с %
  3. Избегай OR, используй UNION или IN
  4. Старайся переписать условия позитивно (без NOT)
  5. Всегда используй правильные типы данных
  6. Профилируй запросы с EXPLAIN
  7. Создавай индексы на часто используемых столбцах в WHERE
  8. Помни, что на большие таблицы (миллионы строк) это критично

Эти правила критичны для production систем с большими объёмами данных.