MySQL 5.5.56 临时 table 在触发器中使用时始终为空,但在手动 运行 查询时有效
MySQL 5.5.56 Temporary table always empty when used in a trigger but works when manually running the query
有人可以帮我解决问题吗?我有这个 SQL 代码,我需要在不起作用的触发器上 运行。但是在 SQL 客户端上手动 运行 运行代码时有效。
SET @sr_id = NEW.purchase_id; /* SET @sr_id = 123456 when run manually */
SET @ndi = (SELECT COUNT(a.id) FROM purchase_rewards a LEFT JOIN item b ON b.id = a.item_id WHERE a.unit_id IS NOT NULL AND COALESCE(b.is_privileged,0) = 0 AND a.purchase_id = @sr_id);
SET @res = @ndi - CEIL(@ndi/2);
DROP TEMPORARY TABLE IF EXISTS for_removal;
CREATE TEMPORARY TABLE for_removal
SELECT ID FROM (
SELECT a.id, @rownum := @rownum + 1 AS `rank` FROM purchase_rewards a LEFT JOIN item b ON b.id = a.item_id
WHERE a.purchase_id = @sr_id AND COALESCE(b.is_privileged,0) = 0
) ft CROSS JOIN (SELECT @rownum := 0) r WHERE `rank` <= @res;
DELETE ta FROM purchase_rewards ta INNER JOIN for_removal tb ON ta.id = tb.id WHERE ta.purchase_id = @sr_id;
该代码查询非“特权”的购买商品,在每个商品上放置一个排名列并删除其中一半。你只得到一半的奖励,这就是重点。该软件是由没有源代码的其他人创建的,因此这是它背后的搭载系统。
我在每个代码之间放置了调试代码,以查看连接是否更改或结果是否为空,但除最后一部分外一切都很好。在删除部分之前我添加了一个调试代码:
SET @icount = (SELECT COUNT(ID) FROM for_removal);
INSERT INTO debug_log SET `log` = @icount;
结果是 table 总是空的。我也尝试将代码转换为存储过程,但我遇到了同样的问题。仅运行在代码有效的地方手动设置代码。
我目前正在解决 CURSOR 和循环删除的问题,但是当有数百个项目时它会变慢。
示例数据:dbfiddle
谢谢!
根据上面的评论,答案是在查询之前设置 @rownum
变量。
SET @rownum = 0;
CREATE TEMPORARY TABLE ...
原因是您不能依赖 CROSS JOIN 中 table 求值的顺序。如果在@rownum 初始化之前评估子查询,则@rownum 将为 NULL,并且任何使用 @rownum := @rownum + 1 递增它的尝试也将产生 NULL。所以 rank
每一行都会有 NULL,并且没有行会满足 WHERE 子句。
至于为什么这在 MySQL 客户端有效但在触发器中无效,我有一个理论:
如果您多次测试查询,会话变量 @rownum
将保持其值。因此,如果您在一个会话中将其设置为某个非 NULL 值,然后在随后的同一会话中测试排名查询,它将递增。
但是如果你 运行 它作为触发器的一部分,它可能每次都是一个全新的会话,并且 @rownum
的值最初是 NULL。
有人可以帮我解决问题吗?我有这个 SQL 代码,我需要在不起作用的触发器上 运行。但是在 SQL 客户端上手动 运行 运行代码时有效。
SET @sr_id = NEW.purchase_id; /* SET @sr_id = 123456 when run manually */
SET @ndi = (SELECT COUNT(a.id) FROM purchase_rewards a LEFT JOIN item b ON b.id = a.item_id WHERE a.unit_id IS NOT NULL AND COALESCE(b.is_privileged,0) = 0 AND a.purchase_id = @sr_id);
SET @res = @ndi - CEIL(@ndi/2);
DROP TEMPORARY TABLE IF EXISTS for_removal;
CREATE TEMPORARY TABLE for_removal
SELECT ID FROM (
SELECT a.id, @rownum := @rownum + 1 AS `rank` FROM purchase_rewards a LEFT JOIN item b ON b.id = a.item_id
WHERE a.purchase_id = @sr_id AND COALESCE(b.is_privileged,0) = 0
) ft CROSS JOIN (SELECT @rownum := 0) r WHERE `rank` <= @res;
DELETE ta FROM purchase_rewards ta INNER JOIN for_removal tb ON ta.id = tb.id WHERE ta.purchase_id = @sr_id;
该代码查询非“特权”的购买商品,在每个商品上放置一个排名列并删除其中一半。你只得到一半的奖励,这就是重点。该软件是由没有源代码的其他人创建的,因此这是它背后的搭载系统。
我在每个代码之间放置了调试代码,以查看连接是否更改或结果是否为空,但除最后一部分外一切都很好。在删除部分之前我添加了一个调试代码:
SET @icount = (SELECT COUNT(ID) FROM for_removal);
INSERT INTO debug_log SET `log` = @icount;
结果是 table 总是空的。我也尝试将代码转换为存储过程,但我遇到了同样的问题。仅运行在代码有效的地方手动设置代码。
我目前正在解决 CURSOR 和循环删除的问题,但是当有数百个项目时它会变慢。
示例数据:dbfiddle
谢谢!
根据上面的评论,答案是在查询之前设置 @rownum
变量。
SET @rownum = 0;
CREATE TEMPORARY TABLE ...
原因是您不能依赖 CROSS JOIN 中 table 求值的顺序。如果在@rownum 初始化之前评估子查询,则@rownum 将为 NULL,并且任何使用 @rownum := @rownum + 1 递增它的尝试也将产生 NULL。所以 rank
每一行都会有 NULL,并且没有行会满足 WHERE 子句。
至于为什么这在 MySQL 客户端有效但在触发器中无效,我有一个理论:
如果您多次测试查询,会话变量 @rownum
将保持其值。因此,如果您在一个会话中将其设置为某个非 NULL 值,然后在随后的同一会话中测试排名查询,它将递增。
但是如果你 运行 它作为触发器的一部分,它可能每次都是一个全新的会话,并且 @rownum
的值最初是 NULL。