🧾SQL Server 用底線分割字串並更新資料庫欄位:避免 STRING_SPLIT 版本限制與 SUBSTRING 537 錯誤的完整教學
在資料工程與系統整合的世界裡,我們常會遇到一種「看似很小、卻很常卡住」的需求:
某個欄位是一段用
_
底線串起來的字串,我要把其中第 3 段、第 4
段抓出來,然後寫回到資料表的兩個欄位。
例如:很多系統把「批號、廠別、日期碼、流程代號」這些資訊,塞在一條檔名或識別字串裡。當資料進到資料庫後,後續報表/查詢就會希望把這些資訊拆開成正式欄位。
但實務上會遇到三個常見坑:
-
STRING_SPLIT在不同版本 SQL Server 的支援差異 -
SUBSTRING會噴 錯誤 537:長度無效 -
CROSS APPLY的欄位引用方式不對會噴 錯誤 207:無效的資料行名稱
這篇文章會用「完全不懂 SQL 的人也能理解的方式」帶你把整套問題解乾淨,最後還會教你如何把結果寫回資料庫(UPDATE)。
一、為什麼
STRING_SPLIT 可能直接不能用?
很多人第一反應會用:
-
STRING_SPLIT(字串, '_')
但你很快就會遇到版本差異:
-
有些文章會教
STRING_SPLIT(text, '_', 1)(多一個參數) -
你的 SQL Server 如果比較舊,就會出現類似錯誤:「引數太多」
重點結論
如果你不能確定 SQL Server 版本是否支援「回傳順序(ordinal)」這個功能,最保險的作法是改用:
✅
CHARINDEX + SUBSTRING(版本相容性最好)
或 ✅ 逐段
CROSS APPLY(工程實務最穩)
二、最常見的爆炸點:SUBSTRING 537(長度無效)
當你用
SUBSTRING(text, start, length)
時,只要 length ≤ 0,SQL
Server 就會噴:
訊息 537:傳遞到 LEFT 或 SUBSTRING 函數的參數長度無效
為什麼會 length 變成 0 或負數?
通常是因為你想取「第 3 段、第 4 段」,但資料裡有:
-
有些字串根本沒有那麼多
_ -
字串格式不一致(某些批次少一段)
-
某些值是 NULL 或空字串
解法核心
✅
一定要先判斷「底線數量是否足夠」
✅ 不足段數:就不要算 SUBSTRING(避免 537)
三、穩定作法:先找出第 N 個底線的位置(逐段往後找)
接下來示範一個「範例資料表」:
-
資料表:
LogTable -
欄位:
RawKey(底線組合字串) -
目標:把 RawKey 的第 3 段、第 4 段拆出來,寫回
ColA、ColB
✅ 分割並顯示(安全版 SELECT)
SELECT
L.Id,
L.RawKey,
X.Part3,
X.Part4
FROM LogTable L
CROSS APPLY (SELECT p1 = CHARINDEX('_', L.RawKey)) A
CROSS APPLY (SELECT p2 = CASE WHEN A.p1 > 0 THEN CHARINDEX('_', L.RawKey, A.p1 + 1) ELSE 0 END) B
CROSS APPLY (SELECT p3 = CASE WHEN B.p2 > 0 THEN CHARINDEX('_', L.RawKey, B.p2 + 1) ELSE 0 END) C
CROSS APPLY (SELECT p4 = CASE WHEN C.p3 > 0 THEN CHARINDEX('_', L.RawKey, C.p3 + 1) ELSE 0 END) D
CROSS APPLY (
SELECT
Part3 = CASE WHEN B.p2 > 0 AND C.p3 > B.p2
THEN SUBSTRING(L.RawKey, B.p2 + 1, C.p3 - B.p2 - 1)
ELSE NULL END,
Part4 = CASE WHEN C.p3 > 0 AND D.p4 > C.p3
THEN SUBSTRING(L.RawKey, C.p3 + 1, D.p4 - C.p3 - 1)
ELSE NULL END
) X
WHERE L.RawKey IS NOT NULL
AND D.p4 > 0 -- 不足 4 段就不要顯示
AND X.Part3 IS NOT NULL
AND X.Part4 IS NOT NULL;
你可以把這段想像成:
-
找第 1 個
_在哪(p1) -
從 p1 後面再找第 2 個
_(p2) -
依此類推找到 p3、p4
-
只有當 p2、p3、p4 都存在時,才允許
SUBSTRING開切
這樣就不會再噴 537。
四、常見錯誤 207:無效的資料行名稱(p3/p4)
如果你把 p1/p2/p3/p4 分散在不同
APPLY、卻在後面用錯別名或引用不到欄位,就會噴:
訊息 207:無效的資料行名稱 'p3'
避免方式
-
保持欄位在同一個
CROSS APPLYscope 內,或確保後面引用的是正確 alias(例如D.p4而不是pos.p4)
五、最後一步:把結果寫回資料庫(UPDATE)
當你確認 SELECT 拆出來是正確的,下一步就是把
Part3/Part4 寫回表格欄位。
✅ UPDATE FROM 寫回(安全版)
UPDATE L
SET
L.ColA = X.Part3,
L.ColB = X.Part4
FROM LogTable L
CROSS APPLY (SELECT p1 = CHARINDEX('_', L.RawKey)) A
CROSS APPLY (SELECT p2 = CASE WHEN A.p1 > 0 THEN CHARINDEX('_', L.RawKey, A.p1 + 1) ELSE 0 END) B
CROSS APPLY (SELECT p3 = CASE WHEN B.p2 > 0 THEN CHARINDEX('_', L.RawKey, B.p2 + 1) ELSE 0 END) C
CROSS APPLY (SELECT p4 = CASE WHEN C.p3 > 0 THEN CHARINDEX('_', L.RawKey, C.p3 + 1) ELSE 0 END) D
CROSS APPLY (
SELECT
Part3 = CASE WHEN B.p2 > 0 AND C.p3 > B.p2
THEN SUBSTRING(L.RawKey, B.p2 + 1, C.p3 - B.p2 - 1)
ELSE NULL END,
Part4 = CASE WHEN C.p3 > 0 AND D.p4 > C.p3
THEN SUBSTRING(L.RawKey, C.p3 + 1, D.p4 - C.p3 - 1)
ELSE NULL END
) X
WHERE L.RawKey IS NOT NULL
AND D.p4 > 0
AND X.Part3 IS NOT NULL
AND X.Part4 IS NOT NULL
AND (
ISNULL(L.ColA,'') <> ISNULL(X.Part3,'')
OR ISNULL(L.ColB,'') <> ISNULL(X.Part4,'')
);
這段 UPDATE 的好處
-
不足段數:不更新、也不報錯
-
只有當值真的不同時才更新:減少鎖表與交易日誌負擔
六、工程師建議:更新前先「預覽差異」
在正式 UPDATE 前,建議先用 SELECT 檢查會修改到哪些資料:
-
舊值 vs 新值
-
RawKey 是否符合你預期的格式
這可以降低「不小心更新錯一整批」的風險。
結語:一套能長期穩定運作的字串拆解更新法
總結你今天學到的關鍵:
✅ 不要迷信
STRING_SPLIT(版本差異很常踩雷)
✅
SUBSTRING
一定要先做條件判斷,不然 537 會一直來
✅ CROSS APPLY 的 alias 與
scope 要一致,不然 207 會爆
✅ UPDATE 要加「只更新變更」條件,避免整批重寫
留言
張貼留言