2017-08-29 62 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