[SQL]SELECT 優先順序與變數指定的異常案例分享

剛好上課時有介紹到 SELECT 和 SQL 變數的使用,朋友就提供一個案例,看來可以把這個案例當成教材來使用。

在 SQL 課程中,很多時候一開始我們都會去介紹在 T-SQL 環境下變數的使用,另外也會去介紹到 SELECT 語法執行的優先順序

順序元素表達式功用
5SELECT<select list >定義所要回傳的欄位
1FROM<table source>定義要查詢的資料表或檢視
2WHERE<search condition>設定資料過濾條件
3GROUP BY<group by list>設定要做群組的欄位
4HAVING<search condition>設定群組後的資料過濾條件
6ORDER BY<order by list>資料輸出排序方式

一般來說這個部分大家看過一次就很容易了解,因此在課程中多半對這個部分就是用一個簡單的範例來做解釋。而晚餐的時候剛好有朋友傳來一個問題,初步看起來並不清楚有甚麼樣的問題,後來他又提供相關的資料,就剛好搭配這樣的狀況,看起來是蠻合適的案例,因此就花了一點時間整理一下。

首先我們先模擬一下實際的問題,並且將問題簡化成為以下的指令

DECLARE @A TABLE
(
	ID INT,
	NAME VARCHAR(32)
)

INSERT INTO @A VALUES ( 1 , 'Beason' ), ( 2 , 'Tom')

DECLARE @S VARCHAR(32)
SELECT @S=NAME FROM @A WHERE ID = 1
SELECT @S
SELECT @S=NAME FROM @A WHERE ID = 99999
SELECT @S
SELECT @S=( SELECT NAME FROM @A WHERE ID=99999 )
SELECT @S

首先在前面一段,從 LINE:1 ~ LINE:8 的部分,我們先用一個 Table 變數 @A 來模擬我們的資料,並且在這裡塞了兩筆的測試資料。在 LINE:10 的部分,我們用一個很長使用的指定變數的方式,將取回的資料列 NAME 的值塞到變數 @S 裡面,而從下面的第一個結果中,可以看到 @S 變數會跟我們所期待的相同,將取得的資料塞入到變數內。

而在 LINE:12 的地方,基本上原本期望因為找不到資料,因此預期變數 @S 應該會是 NULL 才對,但實際從結果上來看,會看到 @S 變數值居然不是我們所期待的,會覺得是不是 SQL Server 抓錯資料,還是語法哪裡寫錯了。

而其實這個部分還是會回到前面 SELECT 語法的優先順序,當我們在一開始的 FROM 決定要去哪裡撈資料,在 WHERE 的部分去進行資料過濾,而在此次的範例中,LINE:12 的部分會因為按照條件會是一個空集合,因此就不會去執行優先順序 5 的 SELECT 的部分,也因此 @S 的變數還會是之前已經執行過的結果,所以會覺得怎麼執行是錯誤的,其實原本的 SQL 是沒有錯的,只是因為在 LINE:12 執行前,@S 變數就已經有存在值了,所以才會有這樣的狀況。

然而在 LINE:14 的地方為什麼又會正確了呢 ? 因為雖然我們在 SELECT 的部分跟之前 LINE:12 類似去指定變數 @S,但是我們用類似子查詢的方式,先利用子查詢去取得欄位的資料之後,然後再將這個變數指定到 @S 裡面,因此就會變成是我們所期望的值了。

因此朋友詢問那是否以後寫 Stored Procedure 或者是比較複雜的 Script 的時候,都要改用 SELECT @變數= ( 子查詢 ) 來執行嗎 ? 我個人是覺得不用因噎廢食,基本上要先了解為什麼在我們的範例中會出錯,主要不是 SQL 語法的錯誤,而是在該變數再使用前已經有值了,因此只要記得在 Stored Procedure 或 Script 內,如果要重複使用相同變數的時候,記得要先將變數值給重置即可,或者是在每一段都不要共用變數,也就都不會有問題了,因此如同上述的範例,也許改成以下的語法,也就沒有問題了。

DECLARE @A TABLE
(
	ID INT,
	NAME VARCHAR(32)
)
INSERT INTO @A VALUES ( 1 , 'Beason' ), ( 2 , 'Tom')

DECLARE @S VARCHAR(32)
SELECT @S=NAME FROM @A WHERE ID = 1
SELECT @S
SET @S = NULL ; -- 將變數 RESET
SELECT @S=NAME FROM @A WHERE ID = 99999
SELECT @S
SET @S = NULL ; -- 將變數 RESET
SELECT @S=( SELECT NAME FROM @A WHERE ID=99999 )
SELECT @S