如何在 MySQL 存储过程中选择与可变数量参数匹配的值

Posted

技术标签:

【中文标题】如何在 MySQL 存储过程中选择与可变数量参数匹配的值【英文标题】:How to select values matching a variable number of parameters in a MySQL stored procedure 【发布时间】:2016-06-04 05:51:45 【问题描述】:

我需要编写一个 mysql 存储过程(从 .NET 调用),它搜索 stoppoints 的表并允许我指定一些可能的 stopMode 值来匹配。

换句话说:

CREATE PROCEDURE getActiveStoppoints(
IN NamePrefix VARCHAR(100),
IN StopModeMatch1 TINYINT(4),
IN StopModeMatch2 TINYINT(4),
IN StopModeMatch3 TINYINT(4),
IN StopModeMatch4 TINYINT(4),
IN StopModeMatch5 TINYINT(4)
)
BEGIN

-- Return all records matching
SELECT sp.* FROM stoppoints sp
WHERE (sp.name LIKE CONCAT(NamePrefix, '%')
AND
(
(sp.stopMode = StopModeMatch1) OR 
(sp.stopMode = StopModeMatch2) OR 
(sp.stopMode = StopModeMatch3) OR 
(sp.stopMode = StopModeMatch4) OR 
(sp.stopMode = StopModeMatch5) 
)
;

END

这种方法看起来非常脆弱——例如,如果我需要传入 6 个可能的 stopMode 值,甚至是 600,该怎么办?当我想以类似方式匹配另外两列时会发生什么?

还有哪些其他可能的方法来实现这一目标?例如,我可以将数组传递给存储过程吗?

我最初通过在VARCHAR 中传递一个逗号分隔的值列表来尝试这个。我最终对这种方法感到非常沮丧,因为:

使用FIND_IN_SET 匹配逗号分隔的字符串不使用任何索引,因此性能很差,因此不是有效的解决方案。 使用PREPAREEXECUTECONCAT 等创建准备好的 SQL 语句感觉很脆弱,而且性能也不是很好。首先,如果与字符串匹配,我需要处理在值周围加上引号。而且我还假设每次运行存储过程时都必须重新创建查询计划? 尝试将 CSV 值拆分到一个临时表中,然后使用子选择 确实 工作,但感觉很 hacky。另外,当您尝试将其分离到存储过程中时,您无法从存储过程中返回表/行;相反,您必须记住临时表名并首先调用存储的过程。它不会超出仅在一列中的使用范围。

当我说我花了几个小时研究这个问题无济于事时,请相信我。如何在 MySQL 中实现这一点,或者它根本不是为这种存储过程设计的?

【问题讨论】:

可能的相关问题:Parameterize an SQL IN clause ;). 这个***.com/questions/8149545/…怎么样 【参考方案1】:

我尝试了一些方法:

    使用常规的 OR 句子(与您的存储过程相同)我得到了一些性能,但写 600 次比较将是一场噩梦。 (0.023s - 137K 随机记录)

    使用临时表。

    CREATE PROCEDURE getActiveStoppoints(
        IN NamePrefix VARCHAR(100),
        IN StopModeMatch1 TINYINT(4),
        IN StopModeMatch2 TINYINT(4),
        IN StopModeMatch3 TINYINT(4),
        IN StopModeMatch4 TINYINT(4),
        IN StopModeMatch5 TINYINT(4)
    )
    BEGIN
    
        -- drop table tempValues;
        create temporary table if not exists tempValues(
            stoppoint int not null primary key
        ) engine=memory;
        truncate tempValues;
        insert ignore into  tempValues(stoppoint) values (StopModeMatch1);
        insert ignore into  tempValues(stoppoint) values (StopModeMatch2);
        insert ignore into  tempValues(stoppoint) values (StopModeMatch3);
        insert ignore into  tempValues(stoppoint) values (StopModeMatch4);
        insert ignore into  tempValues(stoppoint) values (StopModeMatch5);
    
        -- Return all records matching
        SELECT count(*) -- sp.* 
        FROM stoppoints sp
        WHERE sp.name LIKE CONCAT(NamePrefix, '%')
        and sp.stopMode in (select stoppoint from tempValues);
    
    END$$
    

这将适用于 600 多个值,但过程参数是有限的。 也许使用常规表并在过程之外插入每个(600+)值并运行。 (0.24s - 137K 随机记录)

    使用准备好的语句:

    CREATE PROCEDURE getActiveStoppoints(
        IN NamePrefix VARCHAR(100),
        in stopDelimited varchar(255)
    )
    BEGIN
        set @sql = concat("SELECT count(*) FROM stoppoints sp WHERE sp.name LIKE '",NamePrefix,"%'");
        set @sql = concat(@sql," and sp.stopMode in (", stopDelimited ,")" );
    
        PREPARE stmt1 FROM @sql;
        EXECUTE stmt1;
    END$$
    delimiter ;
    

我认为这是最好的解决方案,因为不使用临时表,执行时间与第一种方法相同,第二个参数可以配置为接收600+ csv。

【讨论】:

【参考方案2】:

如果您有 600 个 stopMode 值,最好使用临时表传递这些值。在调用过程之前创建并使用可能的stopMode 值填充临时表并在过程中使用该表。

【讨论】:

【参考方案3】:

我通常认为,当事情变得非常复杂、混乱和难以处理时,这表明使用了错误的工具。

是什么阻碍了您对您感兴趣的每个 stopMode 进行简单查询?

在伪代码中,我的意思是:

rows = array()
forEach(selectedStopMode as $stopMode)
 sql = SELECT [...] WHERE stopMode = $stopMode;
 rows.append(sql.result)

这里有各种理由: 1-MySQL 更有可能比单个大查询更快地解决许多小查询。

2-应用程序会更容易维护。

3-应用程序可以更好地扩展

4-MySQL 将更有可能将结果缓存在查询缓存中,并且更有可能不需要构建临时表,从而获得更好的性能。

除非你告诉我它绝对需要在 MySQL 中完成,否则我会采用这样的解决方案。

【讨论】:

以上是关于如何在 MySQL 存储过程中选择与可变数量参数匹配的值的主要内容,如果未能解决你的问题,请参考以下文章

MySQL存储过程从列表中插入多行

SQL Server - 按可变数量的参数过滤查询

Oracle PL/SQL:如何使用可变数组作为输出参数执行过程?

从 MySQL 数据库中选择数据时,PROCEDURE 的参数数量不正确

如何使用可变参数模板参数保存可变数量的参数?

在preparedStatement中使用可变数量的参数