🧾SQL Server 用底線分割字串並更新資料庫欄位:避免 STRING_SPLIT 版本限制與 SUBSTRING 537 錯誤的完整教學

 在資料工程與系統整合的世界裡,我們常會遇到一種「看似很小、卻很常卡住」的需求:

某個欄位是一段用 _ 底線串起來的字串,我要把其中第 3 段、第 4 段抓出來,然後寫回到資料表的兩個欄位。


例如:很多系統把「批號、廠別、日期碼、流程代號」這些資訊,塞在一條檔名或識別字串裡。當資料進到資料庫後,後續報表/查詢就會希望把這些資訊拆開成正式欄位。

但實務上會遇到三個常見坑:

  1. STRING_SPLIT 在不同版本 SQL Server 的支援差異

  2. SUBSTRING 會噴 錯誤 537:長度無效

  3. 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 段拆出來,寫回 ColAColB

✅ 分割並顯示(安全版 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. 找第 1 個 _ 在哪(p1)

  2. 從 p1 後面再找第 2 個 _(p2)

  3. 依此類推找到 p3、p4

  4. 只有當 p2、p3、p4 都存在時,才允許 SUBSTRING 開切

這樣就不會再噴 537。


四、常見錯誤 207:無效的資料行名稱(p3/p4)

如果你把 p1/p2/p3/p4 分散在不同 APPLY、卻在後面用錯別名或引用不到欄位,就會噴:

訊息 207:無效的資料行名稱 'p3'


避免方式

  • 保持欄位在同一個 CROSS APPLY scope 內,或確保後面引用的是正確 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 要加「只更新變更」條件,避免整批重寫

留言

這個網誌中的熱門文章

🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

🖥️遠端桌面連線完整新手指南:Windows RDP、Chrome Remote Desktop、AnyDesk、TeamViewer 一次搞懂

🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)