【问题标题】:Pass array to MySQL stored routine将数组传递给 MySQL 存储例程
【发布时间】:2011-12-30 06:38:07
【问题描述】:

我需要将字符串数组作为参数传递给 MySQL 存储例程。数组可能很长,其元素数量不固定。然后我想将字符串值放入一个包含一列的内存表中,这样我就可以处理数据了。我不知道这是否可以在 MySQL 中完成。也许需要一些肮脏的解决方法。

例如,我有字符串值:

Banana, Apple, Orange

现在我想从我的 MySQL Fruits 表中获取有关这些水果的数据。伪代码:

create function GetFruits(Array fruitArray) 
   declare @temp table as
      fruitName varchar(100)
   end

   @temp = convert fruitArray to table
   select * from Fruits where Name in (select fruitName from @temp)
end

Microsoft SQL Server 允许您使用 TEXT 数据类型并将数组作为 XML 字符串提交,从而快速创建内存表。但是,我认为这种技术在 MySQL 中是不可能的。

任何有关如何执行此操作的帮助将不胜感激!

【问题讨论】:

    标签: mysql arrays stored-procedures parameter-passing


    【解决方案1】:

    您可以在列表中传递一个字符串并使用prepared statements 来运行查询,例如-

    DELIMITER $$
    
    CREATE PROCEDURE GetFruits(IN fruitArray VARCHAR(255))
    BEGIN
    
      SET @sql = CONCAT('SELECT * FROM Fruits WHERE Name IN (', fruitArray, ')');
      PREPARE stmt FROM @sql;
      EXECUTE stmt;
      DEALLOCATE PREPARE stmt;
    
    END
    $$
    
    DELIMITER ;
    

    使用方法:

    SET @fruitArray = '\'apple\',\'banana\'';
    CALL GetFruits(@fruitArray);
    

    【讨论】:

    • 这种方法会不会容易受到SQL注入攻击?
    • 我觉得和执行简单的INSERT、SELECT等命令是一样的。
    • 这太难了。我会说对临时表使用连接。
    • 如果输入(例如,来自用户)包含未经处理的逗号,这可能会中断。
    • 我真的很惊讶接受的答案会在系统中造成很大的安全漏洞
    【解决方案2】:

    我想出了一个笨拙但实用的解决方案来解决我的问题。它适用于一维数组(更多维度会很棘手)和适合varchar 的输入:

      declare pos int;           -- Keeping track of the next item's position
      declare item varchar(100); -- A single item of the input
      declare breaker int;       -- Safeguard for while loop 
    
      -- The string must end with the delimiter
      if right(inputString, 1) <> '|' then
         set inputString = concat(inputString, '|');
      end if;
    
      DROP TABLE IF EXISTS MyTemporaryTable;
      CREATE TEMPORARY TABLE MyTemporaryTable ( columnName varchar(100) );
      set breaker = 0;
    
      while (breaker < 2000) && (length(inputString) > 1) do
         -- Iterate looking for the delimiter, add rows to temporary table.
         set breaker = breaker + 1;
         set pos = INSTR(inputString, '|');
         set item = LEFT(inputString, pos - 1);
         set inputString = substring(inputString, pos + 1);
         insert into MyTemporaryTable values(item);
      end while;
    

    例如,此代码的输入可以是字符串Apple|Banana|Orange。 MyTemporaryTable 将填充三行,分别包含字符串 Apple、Banana 和 Orange。

    我认为字符串处理的缓慢速度会使这种方法变得无用,但它已经足够快了(对于 1,000 个条目的数组来说只有几分之一秒)。

    希望这对某人有所帮助。

    【讨论】:

      【解决方案3】:

      像这样简单地使用FIND_IN_SET:

      mysql> SELECT FIND_IN_SET('b','a,b,c,d');
              -> 2
      

      所以你可以这样做:

      select * from Fruits where FIND_IN_SET(fruit, fruitArray) > 0
      

      【讨论】:

      • 唯一的缺点是它比存储过程选项慢
      • @kellogs - 你是什么意思?你可以把它放在存储过程中
      • 我不清楚,抱歉。这不是关于 SP 与非 SP,而是关于 WHERE IN 与 FIND_IN_SET。第一种方法是赢家,无论是否在 SP 中。
      • 也适用于整数:FIND_IN_SET(product.Id, '1,2,3')
      【解决方案4】:

      这模拟了一个字符数组,但你可以用 SUBSTR 代替 ELT 来模拟一个字符串数组

      declare t_tipos varchar(255) default 'ABCDE';
      declare t_actual char(1);
      declare t_indice integer default 1;
      while t_indice<length(t_tipos)+1 do
          set t_actual=SUBSTR(t_tipos,t_indice,1);
              select t_actual;
              set t_indice=t_indice+1;
      end while;
      

      【讨论】:

      • 不知道ELT() 函数。但是如何声明字符串变量的“数组”,在这种情况下为t_tipos?如何指定三个字符串 Banana、Apple、Orange?
      【解决方案5】:

      对临时表使用连接。您不需要将临时表传递给函数,they are global。

      create temporary table ids( id int ) ;
      insert into ids values (1),(2),(3) ;
      
      delimiter //
      drop procedure if exists tsel //
      create procedure tsel() -- uses temporary table named ids. no params
      READS SQL DATA
      BEGIN
        -- use the temporary table `ids` in the SELECT statement or
        -- whatever query you have
        select * from Users INNER JOIN ids on userId=ids.id ;
      END //
      DELIMITER ;
      
      CALL tsel() ; -- call the procedure
      

      【讨论】:

      • 使用 TEMPORARY TABLE(甚至 ENGINE=MEMORY)的想法很好,但是您绕开了原始参数类型。您需要将 CSV 值作为参数传递,然后将其解析为该 tmp 表的种子。
      【解决方案6】:

      如果你不想使用临时表,这里有一个类似拆分字符串的函数,你可以使用

      SET @Array = 'one,two,three,four';
      SET @ArrayIndex = 2;
      SELECT CASE 
          WHEN @Array REGEXP CONCAT('((,).*){',@ArrayIndex,'}') 
          THEN SUBSTRING_INDEX(SUBSTRING_INDEX(@Array,',',@ArrayIndex+1),',',-1) 
          ELSE NULL
      END AS Result;
      
      • SUBSTRING_INDEX(string, delim, n) 返回第一个 n
      • SUBSTRING_INDEX(string, delim, -1) 只返回最后一个
      • REGEXP '((delim).*){n}' 检查是否有 n 个分隔符(即您在界限内)

      【讨论】:

        【解决方案7】:

        这有助于我做 IN 条件 希望这会对你有所帮助..

        CREATE  PROCEDURE `test`(IN Array_String VARCHAR(100))
        BEGIN
            SELECT * FROM Table_Name
            WHERE FIND_IN_SET(field_name_to_search, Array_String);
        
        END//;
        

        调用:

         call test('3,2,1');
        

        【讨论】:

        • 这会起作用,但它不能优化查询,因为它必须检查每条记录是否匹配,即如果表中有 1,000,000 行,它将必须检查每行的 id 是否在“集合中”。
        • @RichS 是的。但是我没有找到其他更简单的数组搜索方式。
        • 你将面临全表扫描问题,因为索引在 find_in_set 上不起作用,解决方法是在 where 语句中添加更多条件以减少扫描的行数。
        【解决方案8】:

        我不确定这是否完全回答了问题(不是),但这是我为类似问题提出的解决方案。在这里,我尝试每个分隔符只使用一次 LOCATE()。

        -- *****************************************************************************
        -- test_PVreplace
        
        DROP FUNCTION IF EXISTS test_PVreplace;
        
        delimiter //
        CREATE FUNCTION test_PVreplace (
           str TEXT,   -- String to do search'n'replace on
           pv TEXT     -- Parameter/value pairs 'p1=v1|p2=v2|p3=v3'
           )
           RETURNS TEXT
        
        -- Replace specific tags with specific values.
        
        sproc:BEGIN
           DECLARE idx INT;
           DECLARE idx0 INT DEFAULT 1;   -- 1-origined, not 0-origined
           DECLARE len INT;
           DECLARE sPV TEXT;
           DECLARE iPV INT;
           DECLARE sP TEXT;
           DECLARE sV TEXT;
        
           -- P/V string *must* end with a delimiter.
        
           IF (RIGHT (pv, 1) <> '|') THEN
              SET pv = CONCAT (pv, '|');
              END IF;
        
           -- Find all the P/V pairs.
        
           SELECT LOCATE ('|', pv, idx0) INTO idx;
           WHILE (idx > 0) DO
              SET len = idx - idx0;
              SELECT SUBSTRING(pv, idx0, len) INTO sPV;
        
              -- Found a P/V pair.  Break it up.
        
              SELECT LOCATE ('=', sPV) INTO iPV;
              IF (iPV = 0) THEN
                 SET sP = sPV;
                 SET sV = '';
              ELSE
                 SELECT SUBSTRING(sPV, 1, iPV-1) INTO sP;
                 SELECT SUBSTRING(sPV, iPV+1) INTO sV;
                 END IF;
        
              -- Do the substitution(s).
        
              SELECT REPLACE (str, sP, sV) INTO str;
        
              -- Do next P/V pair.
        
              SET idx0 = idx + 1;
              SELECT LOCATE ('|', pv, idx0) INTO idx;
              END WHILE;
           RETURN (str);
        END//
        delimiter ;
        
        SELECT test_PVreplace ('%one% %two% %three%', '%one%=1|%two%=2|%three%=3');
        SELECT test_PVreplace ('%one% %two% %three%', '%one%=I|%two%=II|%three%=III');
        SELECT test_PVreplace ('%one% %two% %three% - %one% %two% %three%', '%one%=I|%two%=II|%three%=III');
        SELECT test_PVreplace ('%one% %two% %three% - %one% %two% %three%', '');
        SELECT test_PVreplace ('%one% %two% %three% - %one% %two% %three%', NULL);
        SELECT test_PVreplace ('%one% %two% %three%', '%one%=%two%|%two%=%three%|%three%=III');
        

        【讨论】:

          猜你喜欢
          • 2012-10-13
          • 2016-04-27
          • 2010-11-07
          • 1970-01-01
          • 2011-02-13
          • 2014-08-29
          相关资源
          最近更新 更多