2017-08-29 63 views
1

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

例如:

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` 

如果我运行在工作台上面的查询,在第一次运行输出TotalSales = NULL:

NewSales, UsedSales, TotalSales 
3418, 2889, NULL 

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

NewSales, UsedSales, TotalSales 
3418, 2889, 6307.000000000000000000000000000000 

有点奇怪;就好像该变量在设置它的相同查询中不可用一样。我通常通过重复计算而不使用变量来解决这个问题。

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

我确定有一个很好的解释,这里发生了什么,但我很难找到它。任何答案非常感谢。

+0

额外的软件包信息... #MySQL的 的MySQL服务器/现在5.7.16-0ubuntu0.16.04.1 #PHP php7.0常见/现在7.0.8-0ubuntu0.16.04.3 #工作台 版本6.3.9 – Stinkys

回答

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要么是假的要么是空的)......它与COUNT()的工作方式完全匹配。

+0

非常感谢您对Michael的非常详细的解释。我想我可能不恰当地使用变量;我习惯于在同一个函数中使用它们来存放临时数据,这个在MySQL中的实现似乎对我来说并不直观,但它就是这样。 我真的很喜欢你提供的第三个例子,并且会开始使用这种方法。再次感谢。 – Stinkys