在MYSQL中使用动态SQL创建游标

时间:2011-05-21 14:11:29

标签: mysql stored-procedures cursor dynamic-sql

我正在编写一个存储过程,它将游标打开到表中,然后遍历所有记录。在迭代过程中,我根据第一个游标的结果创建一个动态查询。我需要在动态sql上打开游标,但MySQL不允许这样做,因为符合mysql 的官方文档。游标必须在声明处理程序之前声明。变量和条件必须在之前声明声明游标或处理程序“ 。这是脚本

DELIMITER $$

DROP PROCEDURE IF EXISTS sp_test$$

CREATE PROCEDURE `sp_test`()
BEGIN
    -- Declarations

    DECLARE prepared_sql VARCHAR(1000);
    DECLARE index_count INT;

    -- Cursors
    DECLARE cursor1 CURSOR FOR SELECT * from table1;
    -- Continue Handler for Cursor
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
    -- Open cursors
    OPEN cursor1;

    -- Business Logic
    all_alerts_loop: LOOP
        -- Fetch record from cursor1 and create a dynamic sql

        -- Check if cursor has reached to end than leave the loop
        IF no_more_rows THEN
            LEAVE all_alerts_loop;
        END IF;


        WHILE @some_other_variable <> 0
        DO
                              -- I want to open cursor 2 on this sql
            -- set @prepared_sql =  'create dynamic sql here';  
                    END WHILE;

                    -- This works fine
        PREPARE stmt FROM @prepared_sql;
        EXECUTE stmt;

                    -- But can't define cursor here? so what is the solution
                    -- Gives syntax error, I have tried with @prepared_sql also rather than stmt
        DECLARE cursor2 CURSOR FOR stmt;

    END LOOP;

    -- closing cursors
    CLOSE cursor1;
    END$$

DELIMITER ;

知道如何为动态查询创建游标吗?在MYSQL中

7 个答案:

答案 0 :(得分:4)

创建另一个过程并在这个新过程中编写游标代码,然后调用你想要声明游标的过程......

答案 1 :(得分:3)

不允许使用DEFINE cur CURSOR FOR prepared_statement,您必须定义一个有效的SQL语句。好消息是您可以在可以稍后动态创建的视图上定义光标。例如......

DROP PROCEDURE IF EXISTS my_dynamic_proc;
DELIMITER //
CREATE PROCEDURE my_dynamic_proc(tablename varchar(64), fieldname varchar(64), country VARCHAR(64))
BEGIN
    DECLARE adr_value varchar(500);
    DECLARE done BOOLEAN DEFAULT FALSE;
    -- Cursor definition
    DECLARE cur1 CURSOR FOR SELECT address FROM tmp_view_address;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    -- Dynamic view definition and creation
    SET @v = concat('CREATE OR REPLACE VIEW tmp_view_address as SELECT `',fieldname,'` as address FROM ',tablename,' WHERE country_name = "',country,'" group by 1 order by count(1) desc');
    PREPARE stm FROM @v;
    EXECUTE stm;
    DEALLOCATE PREPARE stm;
    -- Open cursor
    OPEN cur1;
    read_loop: LOOP
      FETCH cur1 INTO adr_value;
      IF done THEN
        LEAVE read_loop;
      END IF;
      -- Basic output result
      SELECT concat("My address is ",adr_value);
      -- Use every result in a dynamic update
      SET @u = concat('update ',tablename,' set new_field_address = "',adr_value,'" where country_name = "',country,'" and new_field_address is null');
      PREPARE stm FROM @u;
      EXECUTE stm;
      DEALLOCATE PREPARE stm;
  END LOOP;
  CLOSE cur1;
END//
DELIMITER ;

答案 2 :(得分:1)

由于您无法使用光标进行动态查询,因为SET之前无法DECLARE。此外,您无法将存储过程CALLCURSOR FOR

一起使用
  

DECLARE cursor_name CURSOR FOR select_statement

CALL不是 select_statement

作为解决方法:
您应该创建3个过程而不是1个。

  1. 临时表/视图生成器
    编写存储过程以生成动态查询的临时表或视图。
  2. 计算结果
    您当前的程序将使用CURSOR FOR SELECT FROM临时表。但是您应该首先确保运行临时表/视图过程 - 以获得更新的结果。你不能CALL DECLARE光标之前的程序。CALL这就是你需要第三步的原因。
  3. 一起跑 最终存储过程到CALL生成临时表/视图的过程,然后{{1}}计算结果的预期过程。您应该最后使用最后一个程序作为执行结果的程序。

答案 3 :(得分:0)

我在您的脚本中看到了两个可能的问题:

1)“DECLARE cursor2 CURSOR FOR stmt;”在任何可执行语句之前,可能需要使用所有其他声明将其移动到过程的顶部。

2)游标不能基于动态SQL(即我认为你不能在准备好的语句上构建它)。要解决此限制,可以基于视图声明游标,然后在打开游标之前使用动态SQL创建视图。这种方法的问题是视图是公共的 - 游标声明必须具有视图的固定名称,因此多个并发用户可能无意中看到其他人动态定义的视图。我的解决方法是检查视图的存在并延迟执行该过程,直到视图被删除。这意味着为了在繁忙的环境中可行,您应该创建视图,循环光标,然后尽快删除视图。技术上并不优雅,但这种方法在我的低流量情况下起作用,并且避免了临时表的开销。或者,正如其他人所建议的那样,临时表是线程安全的,但可能会影响性能。

答案 4 :(得分:0)

卡尔尼的做法不那么麻烦。创建两个或多个SP以满足每个条件分支(对于每个条件分支,都需要动态sql)。创建一个包装器SP并将此SP的呼叫扇出到“分支机构”SP。

“准备视图”方法的替代方案在运行该过程时需要更多的CPU周期和内存以及额外的磁盘空间。

答案 5 :(得分:0)

该线程对我有很大帮助,所以这是我的答案,即如何使用一个表中的值通过查询迭代并存储到第二个表或视图中。

lastName = ["Smith", "Jones", "Brown", "Miller", "Brown", "Williams"]
phone = ["111-123-1234","222-123-1234","333-123-1234","444-123-1234","555-123-1234","662-123-1234"]

sample_name = 'David Smith'

def find_details(full_name):
    
    # Split the full name into first and last name
    [first, last] = full_name.split(' ')
    
    # check for the first name in the name list
    for count, f_name in enumerate(name):
        
        # if the first name is listed
        if f_name == first:
            
            # check if the last name at the same index is the last name we need
            if lastName[count] == last:
                
                phone_number = phone[count]
                
                # we have found the person we need
                print(f'{full_name} is located at position {count}. \nPhone number: {phone_number}\n')
                return True
        
    # Otherwise, we didn't find the name
    print(f"The name: {full_name} does not exist in our records. \nPlease try a different name.\n")
    return False
    
find_details('Josh Brown')

find_details('Delilah Le')


答案 6 :(得分:0)

我们可以考虑使用 stmt 来解决这种情况:

  1. 统计查询将返回的所有记录

  2. 循环浏览查询返回的每条记录,为此使用限制。

见下面的例子:

CREATE PROCEDURE `proc_example`(IN p_where text)
BEGIN

    DECLARE v_where text default "";
    DECLARE v_cont integer default 0;
    
    #build a dynamic where
    set v_where = p_where;
    
    #Count query records 
    set @v_sqlSelect_count = 'select count(*) into @v_total ';
    set @v_sqlSelect_count = concat(@v_sqlSelect_count,'from table ');
    set @v_sqlSelect_count = concat(@v_sqlSelect_count,'where ');
    set @v_sqlSelect_count = concat(@v_sqlSelect_count,v_where);
    
    #Executa query
    PREPARE stmt_total FROM @v_sqlSelect_count;
    EXECUTE stmt_total;
    DEALLOCATE PREPARE stmt_total;
    
    #if exists records
    if (@v_total > 0) then
        
        set v_cont = 0;
        
        navRecords:loop
        
            if (v_cont > (@v_total - 1)) then
                leave getAgend;
            end if;
            
            #build select
            set @v_sqlSelect = 'select id,name ';
            set @v_sqlSelect = concat(@v_sqlSelect,'into @id,@name ');
            set @v_sqlSelect = concat(@v_sqlSelect,'from table ');
            set @v_sqlSelect = concat(@v_sqlSelect,'where ');
            set @v_sqlSelect = concat(@v_sqlSelect,v_where);
            set @v_sqlSelect = concat(@v_sqlSelect,' order by id asc limit ',v_cont,',1'); 
            
            #Execute query
            PREPARE stmt_select FROM @v_sqlSelect;
            EXECUTE stmt_select;
            DEALLOCATE PREPARE stmt_select;   
            
            #Do anything with the data @id, @name
            update table1 set desc1 = @name where id1 = @id;
            
            #Next record
            set v_cont = v_cont + 1;
            
        end loop navRecords;
        
    end if;

END