Как реализовать рекурсию в SQL, например вычисление факториала?
Что проверяют
Знаете ли вы рекурсивные CTE и их ограничения в той СУБД, с которой обычно работает .NET-команда, то есть в SQL Server. Факториал тут учебный повод: следом спрашивают про обход иерархии, например дерева категорий или структуры подчинения.
Как отвечать
Рекурсивный CTE состоит из двух частей, объединённых UNION ALL: якорь задаёт стартовую строку, рекурсивная часть ссылается на сам CTE и порождает следующие строки, пока условие в WHERE выполняется.
Факториал в SQL Server: WITH f(n, val) AS (SELECT 1, CAST(1 AS BIGINT) UNION ALL SELECT n + 1, val * (n + 1) FROM f WHERE n < 20) SELECT val FROM f WHERE n = 20;
Детали, которые стоит назвать:
- синтаксис: в T-SQL ключевого слова
RECURSIVEнет, пишется простоWITH.WITH RECURSIVEэто PostgreSQL, MySQL 8 и SQLite; - типы: столбцы якоря и рекурсивной части должны совпадать по типу, иначе будет ошибка несовпадения типов. Отсюда явный
CAST; - переполнение: в
INTпомещается только до 12!, вBIGINTдо 20!, дальше нуженDECIMAL(38, 0); - лимит глубины: по умолчанию 100 уровней, меняется через
OPTION (MAXRECURSION n), 0 снимает ограничение, максимум 32767; - ограничения: в рекурсивной части нельзя использовать агрегаты,
GROUP BY,TOP,DISTINCTи внешние соединения.
Из C# такой запрос вызывают через Dapper или FromSql в EF Core: LINQ рекурсивный CTE не выражает, поэтому иерархию часто кладут в представление или хранимую процедуру. Для сравнения упомяните, что скалярные функции тоже умеют рекурсию, но упираются в предел вложенности в 32 уровня.
Где спотыкаются
Пишут WITH RECURSIVE в SQL Server и получают синтаксическую ошибку, потому что выучили пример для PostgreSQL.
Забывают про тип якоря. Литерал 1 становится INT, и уже на 13! запрос падает с arithmetic overflow.
Не ставят условие остановки или ошибаются в нём. SQL Server оборвёт такой запрос на сотом уровне с ошибкой, а в PostgreSQL встроенного ограничителя нет вовсе: запрос остановит только ошибка переполнения или statement_timeout.