Рекурсивные запросы SQL: инкремент переменной в SELECT
При работе с рекурсивными запросами в SQL часто возникает задача нумерации узлов дерева или отслеживания порядка обхода. Один из популярных приёмов - использование пользовательских переменных для инкремента внутри команды SELECT. В этой статье разберём, можно ли выполнить конструкцию SET @index := 0; SELECT @index++, * FROM T; и какие подводные камни существуют.
Как работает инкремент переменной в SELECT
В MySQL (и некоторых других СУБД) допускается присваивание и инкремент пользовательских переменных прямо в SELECT. Например:
SET @index := 0;
SELECT @index := @index + 1 AS row_num, t.* FROM t;Однако конструкция @index++ не является стандартным оператором SQL. В MySQL оператор ++ не поддерживается - для инкремента нужно использовать @index := @index + 1. Кроме того, порядок вычисления выражений в SELECT может быть недетерминированным, особенно при наличии JOIN, GROUP BY или подзапросов.
Особенности рекурсивных запросов и нумерации
В рекурсивных WITH RECURSIVE запросах (CTE) переменные ведут себя иначе. Каждая итерация рекурсии выполняется в отдельном контексте, поэтому глобальный счётчик через @index может давать непредсказуемые результаты. Рекомендуется использовать ROW_NUMBER() оконную функцию или добавлять порядковый номер через ROW_NUMBER() OVER (ORDER BY ...).
Пример безопасного решения
WITH RECURSIVE tree AS (
SELECT id, parent_id, 1 AS level
FROM nodes WHERE parent_id IS NULL
UNION ALL
SELECT n.id, n.parent_id, t.level + 1
FROM nodes n
JOIN tree t ON n.parent_id = t.id
)
SELECT ROW_NUMBER() OVER (ORDER BY level, id) AS row_num, *
FROM tree;Этот подход гарантирует корректную нумерацию даже при наличии дублирующихся ветвей (копий), поскольку ROW_NUMBER() вычисляется после полного построения дерева.
Почему SET @index и SELECT @index++ может не сработать
- Нестандартный синтаксис:
++не поддерживается в SQL, вызовет ошибку. - Порядок вычисления: переменная может увеличиться несколько раз в одной строке при наличии нескольких ссылок на неё.
- Рекурсия: в
WITH RECURSIVEпеременные не сохраняют состояние между итерациями.
Для простой нумерации строк в плоской таблице можно использовать SET @index := 0; SELECT @index := @index + 1, * FROM T;, но в рекурсивных запросах лучше применять оконные функции.
Альтернативные методы нумерации в рекурсии
Если вам нужно отслеживать порядок обхода дерева (например, для построения пути или материализованного пути), используйте:
- Оконные функции -
ROW_NUMBER(),RANK(). - Генерация пути - конкатенация идентификаторов в строку (например,
CONCAT(path, '/', id)). - Внешний счётчик - через хранимую процедуру с временной таблицей.
Таким образом, финт с SET @index := 0; SELECT @index++, * FROM T; в чистом виде неработоспособен из-за отсутствия оператора ++. Вместо этого используйте @index := @index + 1 для плоских запросов и оконные функции для рекурсивных.