存儲過程接收逗號分隔字符串(如 `'3,6,9'`)作為 `IN` 子句參數,是極常見的需求。直接拼接動態 SQL 雖能工作,但引入注入風險、計劃緩存污染、權限提升、調試困難等嚴重問題。本文提供三種**零動態 SQL**的標準化解決方案。
DECLARE @ManagerIDs VARCHAR(100) = '3,6'; SELECT * FROM HumanResources.Employee WHERE ManagerID IN (@ManagerIDs); -- Msg 245: Conversion failed when converting '3,6' to int.
原因:`ManagerID` 是 `INT`,SQL Server 嘗試將整個字符串 `'3,6'` 隱式轉換為 `INT` 而非拆分為集合。
CREATE PROCEDURE dbo.GetEmployeesByManagerIDs
@ManagerIDs VARCHAR(MAX) -- 逗號分隔字符串
AS
BEGIN
SET NOCOUNT ON;
-- 1. 字符串轉 XML
DECLARE @xml XML = CAST('' + REPLACE(@ManagerIDs, ',', ' ') + ' ' AS XML);
-- 2. XML 撕裂 + 查詢
SELECT e.*
FROM HumanResources.Employee e
INNER JOIN (
SELECT T.c.value('.', 'INT') AS ManagerID
FROM @xml.nodes('/r/v') AS T(c)
) m ON e.ManagerID = m.ManagerID;
END;
CREATE FUNCTION dbo.SplitString_INT
(
@List VARCHAR(MAX),
@Delimiter CHAR(1) = ','
)
RETURNS @Result TABLE (Value INT PRIMARY KEY)
AS
BEGIN
DECLARE @XML XML = CAST('' + REPLACE(@List, @Delimiter, ' ') + ' ' AS XML);
INSERT @Result (Value)
SELECT T.c.value('.', 'INT') FROM @XML.nodes('/r/v') AS T(c);
RETURN;
END;
SELECT e.* FROM HumanResources.Employee e JOIN dbo.SplitString_INT(@ManagerIDs, ',') m ON e.ManagerID = m.Value;
使用 `SqlChars`/`String.Split` 實現,無 XML 開銷,百萬級列表毫秒級返回,需啟用 CLR 集成。
CREATE TYPE dbo.ManagerIDList AS TABLE (ManagerID INT PRIMARY KEY);
CREATE PROCEDURE dbo.GetEmployeesByManagerIDs_TVP
@ManagerIDs dbo.ManagerIDList READONLY
AS
SELECT e.*
FROM HumanResources.Employee e
JOIN @ManagerIDs m ON e.ManagerID = m.ManagerID;
| 維度 | XML | 分割函數 | TVP |
|---|---|---|---|
| 性能 | 中 | 中(XML)/ 高(CLR) | 高(零序列化) |
| 類型安全 | 高 | 高 | 最高(元數據驗證) |
| 最大值數量 | ~10萬 | ~10萬/百萬(CLR) | 無上限(內存允許) |
| 客戶端要求 | 字符串構造 | 字符串構造 | ADO.NET 4.0+ / TVP 支持驅動 |
| 計劃復用 | 好 | 好 | 最好 |
拒絕動態 SQL 不僅是安全合規要求,更是性能穩定性的基石。TVP 是現代首選(類型安全、零解析、計劃友好),XML 分割是通用備胎(無版本依賴、純 T-SQL),CLR 分割是極致性能選項。根據版本、客戶端框架、數據量級選擇最適方案,將「逗號字符串 IN 查詢」納入團隊代碼規範標準庫。