MySQL中用户定义变量的奇怪行为

时间:2017-08-29 01:01:24

标签: mysql mysql-workbench

我正在构建一个报告并使用用户定义的变量来使查询尽可能紧凑。我在MySQL Workbench和PHP中注意到的是他们总是不能正常工作。

例如:

SELECT
@NewSales := SUM(CASE WHEN `v`.`new_used`='N' THEN 1 ELSE 0 END) AS `NewSales`,
@UsedSales := SUM(CASE WHEN `v`.`new_used`='U' THEN 1 ELSE 0 END) AS `UsedSales`,
@UsedSales + @NewSales AS `TotalSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`

如果我在Workbench中运行上述查询,则第一次运行输出TotalSales = NULL:

NewSales, UsedSales, TotalSales
3418, 2889, NULL

如果我刷新查询,输出会产生TotalSales的预期结果:

NewSales, UsedSales, TotalSales
3418, 2889, 6307.000000000000000000000000000000
有点奇怪;几乎就好像变量在设置它的同一查询中不可用。我通常通过在不使用变量的情况下重现计算来解决它。

我的下一个问题是,如果我将同一个查询从Workbench复制到我的应用程序(PHP)中,TotalSales输出将产生" 0"零。

我确信这里有一个非常好的解释,但我找不到它。非常感谢任何答案。

1 个答案:

答案 0 :(得分:1)

您在更改它们的查询中使用用户定义的变量处于非确定性区域,并且解释很简单:您获得的答案实际上来自同一查询的上一次运行。

UDV的作用域是您的个人数据库连接。它们的值在查询之间持续存在,但不在连接之间存在。此查询在运行查询之前从中为您提供@UsedSales + @NewSales的值,而不是之后。 (为什么?因为它就是。没有理由......它可以采取任何一种方式。见下文。)

SET @UsedSales = 1, @NewSales = 2;并再次运行您的查询。 Total在下一次运行中将是3,显然是错误的答案(与您的预期相比)但在服务器可以按照自己喜欢的任何顺序自由解决这些问题的意义上没有错,因为他们看起来喜欢常量。

  

作为一般规则,除了在SET语句中,您不应该为用户变量赋值并在同一语句中读取值。

     

...

     

对于其他语句,例如SELECT,您可能会得到您期望的结果,但这不能保证。

     

...

     

涉及用户变量的表达式的评估顺序未定义。

     

https://dev.mysql.com/doc/refman/5.7/en/user-variables.html

您正试图解决一个并非真正存在问题的问题。

相反,要么这样做:

SELECT
SUM(CASE WHEN `v`.`new_used`='N' THEN 1 ELSE 0 END) AS `NewSales`,
SUM(CASE WHEN `v`.`new_used`='U' THEN 1 ELSE 0 END) AS `UsedSales`,
SUM(CASE WHEN `v`.`new_used` IN ('N','U') THEN 1 ELSE 0 END) AS `TotalSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`;

或者,如果您坚持,请创建一个派生表(此处名为x),并额外添加结果列。

SELECT
x.NewSales, 
x.UsedSales, 
x.NewSales + x.UsedSales AS TotalSales
FROM (
  SELECT
  SUM(CASE WHEN `v`.`new_used`='N' THEN 1 ELSE 0 END) AS `NewSales`,
  SUM(CASE WHEN `v`.`new_used`='U' THEN 1 ELSE 0 END) AS `UsedSales`
  FROM `fi_sales` `s`
  LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`
) x;

这将内部结果具体化为临时表,一旦查询完成执行就会丢弃该表。

或者,如果你真的想要聪明和简短,那就去吧:

SELECT
  COUNT(`v`.`new_used`='N' OR NULL) AS `NewSales`,
  COUNT(`v`.`new_used`='U' OR NULL) AS `UsedSales`,
  COUNT(`v`.`new_used` IN ('N','U') OR NULL) AS `TotalSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`;

这样可行,因为COUNT()只计算具有非空参数的行,并且任何表达式expr OR NULL强制 expr 作为布尔表达式进行计算,因此在逻辑上等同于CASE WHEN expr [IS TRUE] THEN 1 ELSE NULL END,因此只能评估为1(如果 expr 是真的)......或NULL(如果 expr 为false或null)...与COUNT()的工作方式完全匹配。