Recursive stored functions in MySQL
Asked Answered
P

3

10

I'm trying to make a function that recursively builds a path for a specific category

CREATE FUNCTION getPath(inId INT)
RETURNS TEXT
DETERMINISTIC
BEGIN
    DECLARE return_path TEXT;
    DECLARE return_parent_id INT;
    SELECT CONCAT('/', name) INTO return_path FROM article_categories WHERE id = inId;
    SELECT parent_id INTO return_parent_id FROM article_categories WHERE id = inId;

    IF return_parent_id > 0 THEN
        SELECT CONCAT(getPath(return_parent_id), return_path) INTO return_path;
    END IF;

    RETURN return_path;
END

When I try to run this function with a category that has no parents (parent_id = 0) it works fine but when I try a category that has a parent_id > 0 I get 1424 Recursive stored functions and triggers are not allowed.

How do I work around this? I'm going to host this code on a regular web hosting service that should have at least MySQL server version 5.1.


After some help from Ike Walker I have made a precedure instead that works fine

DROP PROCEDURE IF EXISTS getPath;
DELIMITER //
CREATE PROCEDURE getPath(IN category_id INT UNSIGNED, OUT return_path TEXT)
BEGIN
    DECLARE parent_id INT UNSIGNED;
    DECLARE path_result TEXT;

    SET max_sp_recursion_depth=50;

    SELECT CONCAT('/', ac.name), ac.parent_id INTO return_path, parent_id FROM article_categories AS ac WHERE ac.id = category_id;

    IF parent_id > 0 THEN
        CALL getPath(parent_id, path_result);
        SELECT CONCAT(path_result, return_path) INTO return_path;
    END IF;
END //
DELIMITER ;

I then use something like this to call it

CALL getPath(72, @temp); SELECT @temp;
Popham answered 20/9, 2010 at 13:49 Comment(2)
Right now I'm developing on Ubuntu with MySQL-Server version: 5.1.41-3ubuntu12.6 (Ubuntu)Popham
I have found forums.mysql.com/read.php?98,224107,224638#msg-224638 that talks about SET max_sp_recursion_depth=N; where N is the number of recursions to allow. But I still get 1424 Recursive stored functions and triggers are not allowed.Popham
J
13

MySQL does not allow recursive FUNCTIONs, even if you set max_sp_recursion_depth.

It does allow up to 255 recursion in a PROCEDURE if you set max_sp_recursion_depth.

So I recommend that you replace your function with a procedure, using an INOUT variable for the return_path.

Joshia answered 20/9, 2010 at 14:56 Comment(2)
Thank you for sorting this out, I have now made a procedure instead:Popham
DROP PROCEDURE IF EXISTS getPath; DELIMITER // CREATE PROCEDURE getPath(IN category_id INT UNSIGNED, OUT return_path TEXT) BEGIN DECLARE parent_id INT UNSIGNED; DECLARE path_result TEXT; SET max_sp_recursion_depth=50; SELECT CONCAT('/', ac.name) INTO return_path FROM article_categories AS ac WHERE ac.id = category_id; SELECT ac.parent_id INTO parent_id FROM article_categories AS ac WHERE ac.id = category_id; IF parent_id > 0 THEN CALL getPath(parent_id, path_result); SELECT CONCAT(path_result, return_path) INTO return_path; END IF; END // DELIMITER ;Popham
S
7

From the stored procedure in your question, *with the help from @Ike Walker,

DROP PROCEDURE IF EXISTS getPath;
DELIMITER $$
CREATE PROCEDURE getPath(IN category_id INT UNSIGNED, OUT return_path TEXT)
BEGIN
    DECLARE parent_id INT UNSIGNED;
    DECLARE path_result TEXT;
    SET max_sp_recursion_depth=50;

    SELECT CONCAT('/', ac.name), ac.parent_id INTO return_path, parent_id FROM article_categories AS ac WHERE ac.id = category_id;
    IF parent_id > 0 THEN
        CALL getPath(parent_id, path_result);
        SELECT CONCAT(path_result, return_path) INTO return_path;
    END IF;
END $$
DELIMITER ;

Create a function:

DROP FUNCTION IF EXISTS getPath;
CREATE FUNCTION getPath(category_id INT) RETURNS TEXT DETERMINISTIC
BEGIN
    DECLARE res TEXT;
    CALL getPath(category_id, res);
    RETURN res;
END$$

Next, you can select:

SELECT category_id, name, getPath(category_id) AS path FROM article_categories ;
Smiga answered 11/3, 2017 at 10:16 Comment(0)
N
0

With MySQL 8.0 you have yet another possible approach. You can wrap a recursive CTE into your function:


DELIMITER $$
DROP FUNCTION IF EXISTS getPath $$
CREATE FUNCTION getPath(inId INT)
RETURNS TEXT
DETERMINISTIC
BEGIN
DECLARE result TEXT;
WITH RECURSIVE cte_cat(id, parent_id, output_name) AS (
            SELECT ac0.id, ac0.parent_id, CONCAT('/',ac0.`name`) 
                FROM article_categories ac0
                WHERE ac0.id = inId
        UNION ALL
            SELECT ac.id, ac.parent_id, CONCAT('/',ac.`name`,cte.output_name) 
                FROM cte_cat cte
                    INNER JOIN article_categories ac ON ac.id = cte.parent_id
    )
SELECT output_name INTO result FROM cte_cat WHERE parent_id IS NULL;
RETURN result;
END$$
DELIMITER ;

Here is my test table:

CREATE TABLE `article_categories` (
  `id` INT NOT NULL,
  `parent_id` INT NULL,
  `name` VARCHAR(45) NULL,
  PRIMARY KEY (`id`));
  
  INSERT INTO `article_categories`(`id`,`parent_id`,`name`) VALUES(1,NULL,'A');
  INSERT INTO `article_categories`(`id`,`parent_id`,`name`) VALUES(2,1,'A1');
  INSERT INTO `article_categories`(`id`,`parent_id`,`name`) VALUES(3,2,'A1a');
  INSERT INTO `article_categories`(`id`,`parent_id`,`name`) VALUES(4,1,'A2');
  INSERT INTO `article_categories`(`id`,`parent_id`,`name`) VALUES(5,NULL,'B');
Naevus answered 16/3, 2023 at 9:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.