История вопроса:
Стандартный SQL изначально спроектирован как декларативный язык без элементов классического программирования (циклов, ветвлений, прямого перехода), однако с расширениями типа T-SQL/PLSQL стали доступны конструкции вроде WHILE, CASE, даже имитация GOTO.
Проблема:
Итеративные операции (цикл обработки поштучных записей) часто приводят к замедлению работы, особенно при обработке больших объёмов, если не заменить их на "сетевой" (set-based) подход. Ветвления, CASE, IF — очень удобны, но при чрезмерном вложении страдает читаемость и предсказуемость кода.
Решение:
Использование управляющих конструкций должно быть оправдано только когда нельзя обойтись пакетной (bulk/set) обработкой! Для сложных вычислений — допустим небольшой цикл, триггер или CASE. Для массовой обработки — лучше использовать оконные функции или UPDATE с подзапросом.
Пример кода (T-SQL):
DECLARE @i INT = 1 WHILE (@i <= 5) BEGIN IF @i % 2 = 0 PRINT CONCAT('Четное: ', @i) ELSE PRINT CONCAT('Нечетное: ', @i) SET @i = @i + 1 END
CASE-выражение:
SELECT num, CASE WHEN num % 2 = 0 THEN 'even' ELSE 'odd' END AS parity FROM numbers
Ключевые особенности:
Может ли CASE использоваться для фильтрации строк, как WHERE?
Нет! CASE возвращает разные значения, но не фильтрует строки. Частая ошибка — "отфильтровать" через CASE вместо WHERE, результат будет неверный.
Чем отличается WHILE от CURSOR — не одно ли это?
WHILE — базовый цикл, с пользовательским управлением переменной; CURSOR работает по записям таблицы, держит ссылку на строку. CURSOR — более ресурсоёмкий, часто работает намного медленнее для больших данных.
Какой подход быстрее для массовой обработки: цикл WHILE с UPDATE или один set-based UPDATE?
В 99% случаев один сетевой UPDATE (или INSERT) намного быстрее, чем цикл по одной записи (даже если кажется, что так "гибче").
-- Неправильный подход DECLARE @id INT = 1 WHILE (@id <= 100000) BEGIN UPDATE t SET flag=1 WHERE id=@id SET @id = @id + 1 END -- Верно UPDATE t SET flag=1 WHERE id BETWEEN 1 AND 100000
В проекте для обновления статуса по миллиону заказов писали цикл с UPDATE для каждого id. Обработка длилась ~8 часов. При падении на половине — теряли всё, приходилось чинить вручную.
Плюсы:
Минусы:
Переделали на set-based UPDATE с единственным выражением. Время выполнения сократилось до 6 минут, обработка атомарна.
Плюсы:
Минусы: