如何在 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
匹配逗号分隔的字符串不使用任何索引,因此性能很差,因此不是有效的解决方案。
使用PREPARE
、EXECUTE
、CONCAT
等创建准备好的 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 存储过程中选择与可变数量参数匹配的值的主要内容,如果未能解决你的问题,请参考以下文章
Oracle PL/SQL:如何使用可变数组作为输出参数执行过程?