Home > database >  Possible to emulate a recursive CTE?
Possible to emulate a recursive CTE?

Time:06-11

I think the short answer to this is No, but is it possible to emulate a recursive CTE with something like a table function in a database that doesn't have support for a CTE? For example:

-- get weekday names -- 'Monday', 'Tuesday', ...
WITH cte_numbers(n, weekday) AS (
    SELECT 
        0, 
        DATENAME(DW, 0)
    UNION ALL
    SELECT    
        n   1, 
        DATENAME(DW, n   1)
    FROM    
        cte_numbers
    WHERE n < 6
)
SELECT 
    weekday FROM cte_numbers;

For example could the above pattern be rewritten to run in Mysql5.7, or recursion cannot be emulated there?

CodePudding user response:

None of the solutions is very efficient, and most of them involve writing more code than you should. It would be better to upgrade to MySQL 8.0.

SQL works best on sets, not on iteration. So the standard way to generate a series in a single query is to already have a set of rows in a temporary table, and apply some set-based operations to it.

For example:

SELECT 0 AS num UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5;
 ----- 
| num |
 ----- 
|   0 |
|   1 |
|   2 |
|   3 |
|   4 |
|   5 |
 ----- 

With this as the basis, you can do date arithmetic and then extract the weekday names with DATE_FORMAT():

SELECT DATE_FORMAT(CURDATE()   INTERVAL num DAY, '%W') AS weekday
FROM (
  SELECT 0 AS num UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
) AS t;

 ----------- 
| weekday   |
 ----------- 
| Friday    |
| Saturday  |
| Sunday    |
| Monday    |
| Tuesday   |
| Wednesday |
 ----------- 

You could also prepare a fixed base table of integers, fill it with as many as you need, and use it for different purposes.

SELECT DATE_FORMAT(CURDATE()   INTERVAL num DAY, '%W') AS weekday
FROM MySetOfIntegers
WHERE num BETWEEN 0 AND 5;

The suggestion of using an iterative approach would involve writing a lot more code. It will also mean N SQL queries, each generating a separate result set, so that's more code you have to write in your application to fetch all the result sets and append them together.

You could write a recursive stored procedure, but there's a risk of exceeding the thread stack space if you allow deep recursion. The default limit on stored procedure recursion is 0. That is, no recursion is allowed at all unless you set a finite limit. See https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_sp_recursion_depth

Here's an example of a recursive stored procedure:

DROP PROCEDURE IF EXISTS Weekdays;
DELIMITER //
CREATE PROCEDURE Weekdays(IN date DATE, IN num INT)
BEGIN
  IF num >= 1 THEN
    CALL Weekdays(date, num-1);
  END IF;
  SELECT DATE_FORMAT(date   INTERVAL num-1 DAY, '%W') AS weekday;
END//
DELIMITER ;

And calling it. Note it produces multiple result sets.

mysql> set max_sp_recursion_depth=6;

mysql> call Weekdays(CURDATE(), 6);

 ---------- 
| weekday  |
 ---------- 
| Thursday |
 ---------- 
1 row in set (0.00 sec)

 --------- 
| weekday |
 --------- 
| Friday  |
 --------- 
1 row in set (0.00 sec)

 ---------- 
| weekday  |
 ---------- 
| Saturday |
 ---------- 
1 row in set (0.00 sec)

 --------- 
| weekday |
 --------- 
| Sunday  |
 --------- 
1 row in set (0.00 sec)

 --------- 
| weekday |
 --------- 
| Monday  |
 --------- 
1 row in set (0.00 sec)

 --------- 
| weekday |
 --------- 
| Tuesday |
 --------- 
1 row in set (0.00 sec)

 ----------- 
| weekday   |
 ----------- 
| Wednesday |
 ----------- 
1 row in set (0.00 sec)

I may have gotten the recursion off by one somewhere. This actually supports the point that it's not as easy as it sounds to implement a recursive routine.

Oh and you get an error — and no results — if you exceed the recursion limit.

mysql> call Weekdays(CURDATE(), 8);
ERROR 1456 (HY000): Recursive limit 6 (as set by the max_sp_recursion_depth variable) was exceeded for routine Weekdays
  • Related