Skip to content

展開 SELECT * ​

游標停在選取清單的 * 後面時,旁邊會出現「按 Tab 展開所有欄位」,按下去就換成 完整的欄位清單:

text
SELECT *|  FROM dbo.PUBLISHER
  ↓ Tab
SELECT PublisherId, PublisherName, CreatedAt, ModifiedAt FROM dbo.PUBLISHER

提示不是裝飾:使用者不會憑空去試按 Tab,而按下去之前也看不出這一次到底展不展得開 ——衍生資料表少寫一個別名、CTE 裡有個沒有名稱的運算式,都會讓同一個星號變成 展不開的。提示出現與否就是那份判斷的結果,看得到就一定按得動。

提示走的是編輯器自己的提示視窗(與滑鼠停留提示同一套),不會搶走鍵盤焦點; 建議清單開著時它會讓位,兩個小視窗貼在同一行旁邊只會互相擋住。出現與收起由共用的 Editor/CaretHint 在游標、文字、清單、設定或焦點變動後重新判斷——游標跟著編輯位移 時平台不發游標事件,只聽那一個的話,按 Tab 展開之後提示會一直留著。

哪些星號算數 ​

* 在 T-SQL 裡絕大多數時候是乘號。唯一分得開的依據是它前面接什麼:

寫法展開為什麼
SELECT *是選取清單的開頭
SELECT TOP 10 PERCENT WITH TIES *是中間夾的都是 SELECT 的前置詞
SELECT GETDATE(), *是同一份選取清單裡的逗號之後
SELECT a.*是只展開 a 這一個來源
SELECT COUNT(*)否前面是左括號
SELECT 5 * 3、SELECT a * b否前面是數字或識別字
INSERT INTO t (a, *)否那串逗號是資料行清單,不是選取清單
ORDER BY a, *否往回走先遇到 BY

數字刻意只在 TOP 後面才放行。少了這個條件,SELECT 5 * 3 的乘號就會被當成 萬用字元——而那正是第一版的行為。

欄位從哪裡來 ​

判斷「這個星號是不是萬用字元」在 Core/Wildcards/SqlWildcardAnalyzer, 「這個來源給得出哪些欄位」則在 Core/Parsing/SqlColumnSourceResolver—— 後者與建議清單的欄位建議共用。分開的理由是它們回答的是不同的問題: 乘號與萬用字元怎麼分只有展開需要知道,而別名指向哪些欄位是兩個功能都要問的。

來源欄位怎麼取得
資料表、檢視中繼資料的第二層
衍生資料表 (SELECT …) d讀它自己的選取清單
CTE WITH c AS (…)讀主體的選取清單;有寫資料行清單就以它為準
帶資料行清單 … AS T (a, b)就是那份清單;(VALUES …) 只有這條路
暫存資料表 #Loan、資料表變數 @rows讀 CREATE TABLE/DECLARE … TABLE 的資料行清單
巢狀的 *往內遞迴,把最外層的別名一路帶著走

資料行清單只有衍生資料表與 OPENROWSET/OPENQUERY/OPENDATASOURCE 接得住, 文法就是這樣切的:dbo.fn(x) f (NOLOCK) 是舊式資料表提示,形狀一樣,而 NOLOCK 不是保留字,猜括號內容分不出來。

SELECT … INTO #Loan 投影出來的暫存資料表往它讀的那張表追下去,追到中繼資料 也算數——展開等得起一次查詢。QuickInfo 與預覽不等,所以同一張 #Loan 在那兩處 可能什麼都不顯示;那不是分岔,是同一份遞迴在兩種預算下停在不同的地方。

(SELECT * FROM dbo.PUBLISHER c) d 的欄位在外層要寫成 d.欄位——內層的 c 在外面根本不存在。三種欄位別名寫法都讀得出來:Id AS Code、Total = Qty * Price、 ISNULL(Memo, '') Note。

遞迴 CTE 取 UNION ALL 之前那一段的欄位名稱,後面那一段不必看;直接參照自己的 CTE(WITH c AS (SELECT * FROM c))則整個放棄。主體還沒關上時算到文字結尾:游標常在 遞迴的那一段裡,JOIN c ON … = c. 要的正是它自己。

任何一個來源解析不出來就完全不展開,不做部分展開:SELECT Qty * Price 這種 沒有名稱的運算式在外層無從稱呼。少了幾個欄位的 SELECT 仍然執行得動, 卻執行出錯的結果——那比什麼都不做糟糕得多。

同一條規則管住「讀來源」那一步:來源自己帶的括號與尾巴——引數清單、資料表提示、 TABLESAMPLE、時態表的 FOR SYSTEM_TIME …——一律整段跳完。不讀內容與不跳過是兩件事:停在括號前面的話, 後面那個逗號就不再是來源清單的逗號,FROM dbo.Loan l (NOLOCK), dbo.Copy c 的 dbo.Copy 整個消失——而這一次連「解析不出來」都不會發生,展開的是一份看起來 完整的錯誤結果。

加不加別名 ​

敘述裡只有一個資料來源時不加:欄位名稱不可能模稜兩可,補上去只是雜訊。 有兩個以上就一律加上別名,否則 SELECT Name FROM A a JOIN B b 會因為欄位名稱 模稜兩可而執行失敗。使用者自己寫了限定字(a.*、dbo.PUBLISHER.*)時照原文 帶回去:把 dbo.PUBLISHER 改寫成 PUBLISHER 雖然也合法,卻是他沒有要求的改動。

方括號跟著「插入物件時加上方括號」走,與建議清單提交時是同一條規則。

欄位怎麼排 ​

「SELECT * 展開後的欄位排版」有三種選擇,差別只在整份放不下一行的時候:

設定放得下一行放不下
放得下就排成一行(預設)一行每欄一行
永遠每欄一行每欄一行每欄一行
依行寬排滿一行一行放多個欄位

預設值是前者:絕大多數的 SELECT * 攤開來還是一行讀得完,而一張一百多欄的 資料表攤成一行等於逼使用者橫向捲動去讀自己剛剛產生的東西。

每欄一行是唯一一個輸出形狀只由欄位數量決定的模式——換行位置不受欄位名稱 長度與 * 的縮排位置影響,所以新增或移除一個欄位只會動到一行,比對版本差異 最清楚。另外兩種都由字元數決定換行點:改一個欄位名,後面的排法會整段跟著變。

換行一律對齊原本 * 的位置。第一個欄位永遠留在原地,把它推到下一行只會讓 SELECT 孤零零地留在上一行。前導空白裡的定位字元原樣保留、其餘換成空白: 定位字元換成空白會讓對齊在定位寬度不是 4 的機器上跑掉。

行寬本身(120 個字元)不做成設定。排法是使用者感覺得到的,分界點落在 118 還是 124 他不會有意見。

Tab 的四種意思 ​

Tab 在編輯器裡可能是提交建議清單、前進到下一個 Snippet 欄位、展開萬用字元, 或一般縮排。全部由 SqlTabCommandHandler 依這個順序判斷;Shift+Tab 只在 Snippet session 開著時回到上一欄,其餘交回編輯器。

**清單開著時一定要讓開。**不先問過 broker 的話,使用者按 Tab 想提交清單選取項時, 可能會變成 Snippet 跳欄或展開萬用字元。Snippet 與萬用字元不能各自匯出一個 Before=default 的 Tab handler;兩者的先後順序沒有保證。

欄位已經在快取裡(建議清單開過一次就預熱好了)就在按鍵的同一個回合裡改完, 看起來就像按鍵直接改了文字;沒命中才去查資料庫,而那一次 Tab 仍然算被處理掉 ——不吞掉的話編輯器會先插入一個定位字元,等查詢回來時要展開的位置早就被它推走了。 要換掉的範圍以 ITrackingSpan 記住,查詢期間使用者繼續打字也不會換錯地方。

以 Apache-2.0 授權條款發布 · 專為 SSMS 22 設計