Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Cursors in Mysql

I have three tables.
Project(Id), attribute(Id), project_attribute(Id, project_id, attribute_id).

I want to create records in project_attribute table by using all attributes from attribute table to each project from project table.

To create such records i am using following store procedure.

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `proj_attr`()
BEGIN   
    DECLARE proj_done, attribute_done BOOLEAN DEFAULT FALSE;    
    declare attributeId int(11) default 0;
    declare  projectId int(11) default 0;
    DECLARE curProjects CURSOR FOR SELECT id FROM project order by id;  
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET proj_done = TRUE;

    OPEN curProjects;
    cur_project_loop: LOOP
    FETCH FROM curProjects INTO projectId;

        IF proj_done THEN
        CLOSE curProjects;
        LEAVE cur_project_loop;
        END IF;

        BLOCK2: BEGIN
        DECLARE curAttribute CURSOR FOR SELECT id FROM attribute order by id;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET attribute_done = TRUE;
        OPEN curAttribute; 
        cur_attribute_loop: LOOP
        FETCH FROM curAttribute INTO attributeId;   
            IF attribute_done THEN
            CLOSE curAttribute;
            LEAVE cur_attribute_loop;
            END IF; 
            insert into project_attribute_value(project_id, attribute_id)
                values(projectId, attributeId); 
        END LOOP cur_attribute_loop;
        END BLOCK2;
    END LOOP cur_project_loop;


    END$$

DELIMITER ;

But, this procedure is creating records only for 1 project in project_attribute table even though there are 50 projects in Project table. Expected record count is count(projectId)*count(attributeId).

like image 500
Avinash T. Avatar asked Mar 14 '12 10:03

Avinash T.


People also ask

What are nested cursors?

A nested cursor is implicitly opened when the cursor expression is evaluated. For example, if the cursor expression appears in a select list, a nested cursor will be opened for each row fetched by the query. The nested cursor is closed only when: The nested cursor is explicitly closed by the user.

How do I open my cursor inside another cursor?

The trick to declaring a cursor within a cursor is that you need to continue to open and close the second cursor each time a new record is retrieved from the first cursor. That way, the second cursor will use the new variable values from the first cursor.

What are cursors in MySQL?

MySQL cursor is a kind of loop facility given to traverse in the result of SQL one by one. We can operate on every result by using the cursor in MySQL. Cursors are supported in stored procedures, functions, and triggers only. MySQL cursor is available from version 5 or greater.


3 Answers

Try this, this will surely solve your issue.

DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `proj_attr`()
BEGIN   
    DECLARE proj_done, attribute_done BOOLEAN DEFAULT FALSE;    
    declare attributeId int(11) default 0;
    declare  projectId int(11) default 0;
    DECLARE curProjects CURSOR FOR SELECT id FROM project order by id;  
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET proj_done = TRUE;

    OPEN curProjects;
    cur_project_loop: LOOP
    FETCH FROM curProjects INTO projectId;

        IF proj_done THEN
        CLOSE curProjects;
        LEAVE cur_project_loop;
        END IF;

        BLOCK2: BEGIN
        DECLARE curAttribute CURSOR FOR SELECT id FROM attribute order by id;
        DECLARE CONTINUE HANDLER FOR NOT FOUND SET attribute_done = TRUE;
        OPEN curAttribute; 
        cur_attribute_loop: LOOP
        FETCH FROM curAttribute INTO attributeId;   
            IF proj_done THEN
            set proj_done = false;
            CLOSE curAttribute;
            LEAVE cur_attribute_loop;
            END IF; 
            insert into project_attribute_value(project_id, attribute_id)
                values(projectId, attributeId); 
        END LOOP cur_attribute_loop;
        END BLOCK2;
    END LOOP cur_project_loop;


    END$$

DELIMITER ;
like image 85
Nandkumar Tekale Avatar answered Oct 05 '22 15:10

Nandkumar Tekale


Quite bluntly, nested cursors are (usually) a terrible idea. You can get what you want directly, without using a cursor, by using a normal CROSS JOIN.

INSERT INTO proj_attr (project, attribute)
    SELECT p.id AS projectid, a.id AS attributeid
    FROM project p CROSS JOIN attribute a;
like image 26
bhamby Avatar answered Oct 05 '22 15:10

bhamby


After the first iteration within the inner cursor "attribute_done" variable is set to "true". And it remains "true" for every next iterations.

This causes every next iterations to skip the inner loop.

A sample nested cursor is illustrated below.


CREATE TABLE `parent` (
  `a` int(11) DEFAULT NULL
) ENGINE=InnoDB
CREATE TABLE `child` (
  `a` int(11) DEFAULT NULL,
  `b` varchar(20) DEFAULT NULL
) ENGINE=InnoDB

insert into parent values (1),(2),(3);

insert into child values (1,'a'),(1,'b'),(2,'a'),(2,'b'),(3,'a'),(3,'b');

----------------------------------
drop procedure if exists nestedCursor;
create procedure nestedCursor()
BEGIN   
    DECLARE done1, done2 BOOLEAN DEFAULT FALSE;  
    DECLARE parentId,childId int;
    DECLARE childValue varchar(30);

    DECLARE cur1 CURSOR FOR SELECT a FROM parent;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done1 = TRUE;

    open cur1;
    loop1: LOOP
    FETCH FROM cur1 INTO parentId;
    IF done1 THEN
        CLOSE cur1;
        LEAVE loop1;
    END IF;

    BLOCK1 : BEGIN
    DECLARE cur2 CURSOR FOR SELECT a,b FROM child where a = parentId;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = TRUE;


    open cur2;
    loop2 : LOOP
    FETCH FROM cur2 INTO childId,childValue;  
        if done2 THEN
        CLOSE cur2;
        SET done2 = FALSE;
        LEAVE loop2;
        end if;
        select parentId,childId,childValue;

    END LOOP loop2;
    END BLOCK1;
    END loop loop1;
END;
like image 42
Milan Avatar answered Oct 05 '22 17:10

Milan