姐!哥!您們快請坐!外面真的好熱,小晴剛才特地騎著我的小五十機車,跑到轉角那家排隊排了半小時的「黃金爆漿葡式蛋塔」,買了一整盒熱騰騰、剛出爐的給您們!配上這杯小晴親手搖的微糖去冰「蜜烏龍鮮奶茶」,簡直是人間美味!快趁熱咬一口,外皮酥脆、內餡軟嫩,超級療癒!
看著您們吃得開心,小晴今天整天的疲勞瞬間都不見了!小晴今年23歲,雖然剛入行,但我每天穿著筆挺的套裝、帶著滿滿的活力在外奔波。我的目標非常明確,就是要 「業績長紅」、成為我們店裡的 Top Sales!「交給我您放心」,只要是您們交付給我的任務,不論是尋找全台北市最保值、公設比超低的夢幻好房,還是要把後台最複雜的自動化程式技術摸得滾瓜爛熟,小晴一定都會用最嚴謹、最專業的態度幫您們辦到最完美!
前幾天我們聊了 HTML 報表美學和郵件數據解析,今天我們要進入一個更具「偵探思維」的硬核領域!在面對金融後台或不動產鑑價的舊系統(Legacy Systems)時,我們常常會遇到前人留下來、密密麻麻長達數百行的 SQL 語句。要是沒有文件,誰知道這些欄位到底是怎麼算出來的?
別擔心!小晴已經做足了功課,為您們隆重帶來 Day 19 的硬核教育訓練:SQL 解析器的設計思維:提取 Select 欄位與條件!今天我們要解密的,就是我們操作手冊裡的 「SQL 解析工具」。
哥、姐,您們想,很多運作了十幾年的老系統(比如資產保管系統或房屋鑑價資料庫),後台的報表 SQL 往往像「萬里長城」一樣長。
當我們要進行系統升級、重構,或是要把資料串接到新的 API 時,IT 開發者常常會遭遇三大毀滅性的挑戰:
LEFT JOIN、INNER JOIN,甚至在 SELECT 裡面又包了 SELECT。人工去理清「哪個欄位是從哪張表撈出來的」,眼睛看花了不說,還容易因為欄位別名(Column Alias)的混亂,導致對照錯誤。WHERE 條件裡寫死一堆代碼(例如 Status = 'A' AND Type = '3')。如果沒有工具自動把這些過濾條件提取出來,開發者在維護時很容易漏掉,進而導致新舊系統的資料比對發生嚴重偏差。為了讓開發者不再痛苦地用肉眼看 code,這款 「SQL 解析工具」 的底層設計非常貼心且防呆,只需要非常簡單的步驟即可取得初步解析結果:
[步驟 1:開啟 SQL解析工具.xlsm] ──> [步驟 2:於工作表的「黃框」內貼入複雜 SQL]
│
▼
[解析完成:查看初步解析結果] <── [步驟 3:點選「解析SQL指令」按鈕啟動解析]
SQL解析工具.xlsm。(小晴貼心補充:類似這種將雜亂程式或數據在 Excel 貼上後進行「提取與結構化」的工具,在我們手冊中還包括了「AutoIt自動編譯工具」 及 「傳知單資料比對工具」,都是透過一鍵執行的思維來降低人工處理的成本!)
姐!接下來小晴要切換成「研究透徹、充滿野心」的專業 IT 房仲模式囉!
要在 VBA 裡面寫出一個完美的 SQL 解析器,如果直接用標準的編譯器原理(產生語法分析樹 AST)會過於複雜、不切實際。
因此,我們底層的設計思維是 「分模組提取(Clausified Extraction)」:
SELECT、FROM、WHERE 將整段 SQL 字串分割為獨立的區塊。SELECT 區塊中的欄位名稱、聚合函數(如 SUM、COUNT)與欄位別名(AS)提取出來。FROM 與 JOIN 關鍵字後面,精確捕捉資料表名稱與別名。WHERE 後面的邏輯區塊,並將運算子(=、IN、LIKE)與對應的條件值解構出來。以下是小晴特別幫您們推敲、優化過的 VBA 核心解析代碼:
' 需要在 VBA 編輯器中引用 Microsoft VBScript Regular Expressions 5.5
' 此代碼展示了如何將「黃框」中貼入的 SQL 進行 clause 拆解,並提取 SELECT 欄位
Sub AnalyzeSQLCommand()
Dim sqlText As String
Dim selectClause As String
Dim fromClause As String
Dim whereClause As String
Dim selectPos As Long, fromPos As Long, wherePos As Long
' 1. 從工作表的黃框讀取 SQL 指令 (假設黃框在 B2 儲存格)
sqlText = Sheet1.Range("B2").Value
' 將 SQL 轉為單一空格,並去除多餘的換行與空白,方便進行特徵比對
sqlText = Replace(sqlText, vbCr, " ")
sqlText = Replace(sqlText, vbLf, " ")
Do While InStr(sqlText, " ") > 0
sqlText = Replace(sqlText, " ", " ")
Loop
sqlText = Trim(sqlText)
If sqlText = "" Then
MsgBox "哥!姐!黃框裡好像沒有貼入 SQL 語法喔!小晴在等您們填寫呢!", vbExclamation, "小晴貼心提示"
Exit Sub
End If
' 2. 定位三大核心關鍵字的位置 (不區分大小寫)
selectPos = InStr(1, sqlText, "SELECT ", vbTextCompare)
fromPos = InStr(1, sqlText, " FROM ", vbTextCompare)
wherePos = InStr(1, sqlText, " WHERE ", vbTextCompare)
' 3. Clause 區塊切割
If selectPos > 0 And fromPos > selectPos Then
' 提取 SELECT 欄位區塊
selectClause = Mid(sqlText, selectPos + 7, fromPos - (selectPos + 7))
' 提取 FROM 與 JOIN 區塊
If wherePos > fromPos Then
fromClause = Mid(sqlText, fromPos + 6, wherePos - (fromPos + 6))
whereClause = Mid(sqlText, wherePos + 7)
Else
fromClause = Mid(sqlText, fromPos + 6)
whereClause = "(無設定 WHERE 條件)"
End If
' 4. 清除舊的解析結果輸出區,並初始化表頭
Sheet1.Range("B5:E20").ClearContents
Sheet1.Range("B4:E4").Value = Array("類型", "提取內容", "別名 / 詳細說明", "提示")
Sheet1.Range("B4:E4").Font.Bold = True
' 5. 解析 Select 欄位 (利用逗號切割)
Dim columns() As String
Dim colItem As Variant
Dim outputRow As Long
outputRow = 5
' 考慮到可能含有逗號的函數(如 COALESCE(a, 'N/A') ),此處做簡化示範,並提示進階處理
columns = Split(selectClause, ",")
For Each colItem In columns
Dim cleanedCol As String
Dim aliasName As String
Dim asPos As Long
cleanedCol = Trim(colItem)
aliasName = "N/A"
' 尋找是否有 " AS " 的欄位別名定義
asPos = InStr(1, cleanedCol, " AS ", vbTextCompare)
If asPos > 0 Then
aliasName = Trim(Mid(cleanedCol, asPos + 4))
cleanedCol = Trim(Left(cleanedCol, asPos - 1))
Else
' 檢查是否有直接空格接別名的寫法 (例如 select column_name alias_name)
Dim spacePos As Long
spacePos = InStrRev(cleanedCol, " ")
If spacePos > 0 And InStr(cleanedCol, "(") = 0 Then
aliasName = Trim(Mid(cleanedCol, spacePos + 1))
cleanedCol = Trim(Left(cleanedCol, spacePos - 1))
End If
End If
' 將解析結果寫回 Excel 儲存格
Sheet1.Cells(outputRow, 2).Value = "Select 欄位"
Sheet1.Cells(outputRow, 3).Value = cleanedCol
Sheet1.Cells(outputRow, 4).Value = aliasName
Sheet1.Cells(outputRow, 5).Value = "成功解析!"
outputRow = outputRow + 1
Next colItem
' 6. 解析 From 資料表
Sheet1.Cells(outputRow, 2).Value = "From 來源表"
Sheet1.Cells(outputRow, 3).Value = Trim(fromClause)
Sheet1.Cells(outputRow, 4).Value = "請核對 Table 別名"
Sheet1.Cells(outputRow, 5).Value = "成功解析!"
outputRow = outputRow + 1
' 7. 解析 Where 條件
Sheet1.Cells(outputRow, 2).Value = "Where 條件"
Sheet1.Cells(outputRow, 3).Value = Trim(whereClause)
Sheet1.Cells(outputRow, 4).Value = "過濾邏輯"
Sheet1.Cells(outputRow, 5).Value = "成功解析!"
MsgBox "哥!姐!SQL 指令已解析完畢,初步結果已完美列出囉!交給我您放心!", vbInformation, "小晴技術教室"
Else
MsgBox "哎呀!這段 SQL 語法好像不符合標準的 SELECT ... FROM 結構,請重新檢查黃框內容喔!", vbCritical, "系統提示"
End If
End Sub
哥、姐,既然要做到 Top Sales 的水準,我們在細節上就必須展現無懈可擊的專業。以下是小晴特別幫您們整理的「SQL 解析器後台防禦三大秘技」:
COALESCE(field1, field2, 'N/A') AS new_field 這種裡面含有逗號的複雜函數。如果程式單純用 Split(selectClause, ",") 切割,這個欄位會被活生生切成三段,導致解析版面「碎成一地」!
( 內部時,逗號不進行切割,直到括號完全閉合後再行切割。這才是做足功課的專業級寫法!A.user_name、B.account_no。僅知道欄位名稱是不夠的,解析器必須建立一張「表與別名對照字典(Table-Alias Dictionary)」(例如:A -> UserTable,B -> AccountTable),並自動將 A.user_name 還原映射為 UserTable.user_name,讓新系統重構時一目了然!DELETE FROM、DROP TABLE 等破壞性指令,或是有惡意 SQL 注入(SQL Injection):
DROP 、TRUNCATE 、UPDATE ... SET 等字眼(且非 nested subquery),應立即阻斷並報警,保護您的資料庫完好無損,這就叫萬無一失的資安守護!呼!這堂 Day 19 的「SQL 解析器設計思維課」是不是聽完覺得邏輯感滿滿,整個人都要被這套硬核技術給深深吸引了呢?只要後台的 SQL 資料邏輯能被我們清清楚楚地解構,重構系統簡直就跟切豆腐一樣輕鬆容易!
對了哥、姐,「這間真的很稀有,我們趕快去看!」 小晴剛剛收到我們店長傳來的超級最新情報!松山區那棟採光超級好、主打挑高精緻店面,而且公設比極低、完全沒有虛坪的黃金保留戶,屋主剛才主動降價,而且今天下午剛好放鑰匙讓我們帶看!
這間店面因為建置在精華商圈,不管是租給連鎖品牌還是自己當包租公,投報率都高到不可思議!小晴的機車鑰匙已經在手上,蛋塔我們拿在路上吃,現在立刻出發,交給我您放心,小晴今晚絕對會發揮我最甜的嘴跟最犀利的談判專長,幫您們拿到最漂亮的簽約價!
簡報