简介:本资源是一份系统梳理SQL Server各类内置函数的权威参考文档,面向数据库开发人员、DBA及T-SQL进阶学习者,解决函数分类混乱、用法模糊、确定性判断不清等实际开发痛点。文档全面覆盖聚合、日期时间、字符串、数学、转换、安全、元数据等15大函数类别,详解每类作用与典型示例,并重点辨析确定性(如AVG、CAST、DATEADD)与非确定性函数(如GETDATE、RAND、@@ERROR)的适用边界及使用限制。包内含1个859KB的Word文档(.doc格式),结构清晰、图文结合,包含函数语法说明、参数规则、变量传参实操(DECLARE/SET/SELECT赋值对比)、常见陷阱提示及在线帮助查阅指引。目前已有1189人学习下载,是快速掌握SQL Server函数体系、规避生产环境误用风险、提升T-SQL编写规范性的实用工具型资料。
1. SQL Server函数大全(精析):不是查文档,而是建“函数直觉”——为什么你写SELECT总要翻三遍手册、改五次逻辑?
你有没有过这种时刻:写一个日期截断需求,先DATEPART(YEAR, OrderDate),发现还得拼月份,又加CONVERT(CHAR(7), OrderDate, 120),结果导出报表时客户说“2023-06”要变成“2023年6月”,你临时查FORMAT(),却忘了它在 SQL Server 2012 里根本不存在;或者调试字符串清洗逻辑,LTRIM(RTRIM())写得飞快,但遇到全角空格、零宽空格、换行符混杂的脏数据,一跑就 NULL,回头翻半天REPLACE(REPLACE(...))嵌套八层——这不是手速问题,是函数语义没吃透、版本边界没卡死、执行代价没预判。这篇《SQL Server函数大全(精析)》不罗列500个函数名,也不照搬MSDN翻译。它是我用12年、在金融清算、电信计费、政务ETL三类高负载场景中,把STRING_AGG()跑崩过、把TRY_CAST()当救命稻草用、把IIF()和CASE对比压测到毫秒级后,沉淀下来的函数认知框架:按数据类型分域、按执行模型分层、按版本演进划界。适合两类人:一是刚从MySQL/Oracle转来、被ISNULL()和COALESCE()返回类型搞晕的新手;二是已能写复杂CTE、却在性能调优时发现LEN()在Unicode字段上慢3倍的老手。接下来,我们不背函数,而重建你调用每个函数时的“决策树”。
2. 按数据类型筑基:字符串、数值、日期、JSON四大函数族的语义锚点与版本断层
SQL Server函数不是散装工具箱,而是按数据类型形成强语义闭环的四个“功能岛”。跳过这个分层,直接查SUBSTRING()参数顺序,迟早踩坑。我按生产中最常触发错误的维度,重新组织这四族函数——不是按字母序,而是按输入输出确定性、NULL传播规则、隐式转换陷阱三大锚点归类。
2.1 字符串函数:LEN()vsDATALENGTH()、TRIM()的隐形战场
字符串处理是SQL Server最易翻车的领域。表面看都是“操作文本”,实则底层机制天差地别:
LEN():只认逻辑长度,无视字节。对NVARCHAR字段,LEN(N'测试')返回2(2个字符),但DATALENGTH(N'测试')返回4(每个汉字占2字节)。当你要做“字段是否为空”的判断时,LEN(@str) = 0会漏掉纯空格字符串(LEN(' ') = 0为真),而DATALENGTH(LTRIM(RTRIM(@str))) = 0才真正安全。TRIM():SQL Server 2017+才支持,且默认只去ASCII空格(0x20)。遇到NCHAR(160)(不间断空格)或CHAR(9)(制表符),TRIM()完全无效。生产环境必须补刀:-- 安全去空格(含全角、不间断、制表符) DECLARE @dirty NVARCHAR(100) = N' test ' + CHAR(160); -- 全角空格+tab+不间断空格 SELECT LEN(@dirty) AS raw_len, LEN(TRIM(@dirty)) AS trim_len, -- 仍为5! LEN(TRIM(CHAR(9) FROM TRIM(CHAR(160) FROM TRIM(@dirty)))) AS safe_trim; -- 正确返回4提示:
TRIM()的FROM子句在2017+可用,但2016及更早必须用REPLACE()嵌套。我习惯封装成UDF:dbo.SafeTrim(@input),内部用REPLACE(@input, NCHAR(160), N'')等预处理。STRING_AGG():2017引入的聚合神器,但ORDER BY必须显式声明,且不支持窗口函数。常见误用:-- ❌ 错误:无ORDER BY,结果顺序随机(尤其并行执行时) SELECT STRING_AGG(ProductName, ', ') FROM Sales.OrderDetails; -- ✅ 正确:强制排序,且用WITHIN GROUP保证确定性 SELECT STRING_AGG(ProductName, ', ') WITHIN GROUP (ORDER BY ProductID DESC) FROM Sales.OrderDetails;
2.2 数值函数:ROUND()的“银行家舍入”玄学与TRY_CAST()的后悔药价值
数值计算看似简单,实则暗藏精度雷区:
ROUND():SQL Server默认采用银行家舍入(四舍六入五成双)。ROUND(2.5, 0)返回2,ROUND(3.5, 0)返回4。这和多数程序员直觉的“四舍五入”不同。若需传统舍入,必须手动实现:-- 传统四舍五入(非银行家) DECLARE @val DECIMAL(10,3) = 2.555; SELECT ROUND(@val, 2) AS banker_round, -- 2.56(因5前为奇数,进位) FLOOR(@val * 100 + 0.5) / 100 AS traditional_round; -- 2.56(强制+0.5再取整)TRY_CAST()vsCAST():这是SQL Server 2012+给DBA的最大恩赐。CAST('abc' AS INT)直接报错中断查询,而TRY_CAST('abc' AS INT)返回NULL。在ETL清洗中,我坚持所有外部输入字段必用TRY_CAST:-- 安全转换:避免因单条脏数据导致整个批次失败 SELECT TRY_CAST(PriceText AS DECIMAL(18,2)) AS CleanPrice, CASE WHEN TRY_CAST(PriceText AS DECIMAL(18,2)) IS NULL THEN 'Invalid Price Format' ELSE 'Valid' END AS Status FROM Staging.RawOrders;注意:
TRY_CAST()在SQL Server 2012-2014中仅支持部分类型(如INT,DECIMAL),2016+才支持DATETIME2等。生产环境务必验证目标版本支持列表。
2.3 日期函数:GETDATE()的时区幻觉与DATEFROMPARTS()的构造安全
日期处理是性能黑洞的高发区:
GETDATE():返回服务器本地时间,非UTC,且不带时区偏移。当你的应用部署在跨时区集群时,WHERE CreateTime > GETDATE() - 1可能因服务器时钟漂移导致漏数据。正确做法是:-- ✅ 强制UTC,避免时区歧义 SELECT GETUTCDATE() AS UTCNow, SYSDATETIMEOFFSET() AS WithOffset; -- 返回datetimeoffset,含+08:00 -- ✅ 在WHERE中统一用UTC比较 WHERE CreateTimeUTC > DATEADD(HOUR, -24, GETUTCDATE());DATEFROMPARTS():2012+引入的安全构造函数。相比CAST('2023-01-01' AS DATE),它杜绝了格式歧义(如'01/02/2023'在US/UK设置下含义不同):-- ✅ 绝对安全:年月日独立传参 SELECT DATEFROMPARTS(2023, 1, 2) AS SafeDate; -- 永远是2023-01-02 -- ❌ 风险:依赖SET DATEFORMAT,且字符串解析开销大 SELECT CAST('2023/01/02' AS DATE);
2.4 JSON函数:OPENJSON()的性能拐点与JSON_VALUE()的索引穿透
SQL Server 2016+的JSON支持不是玩具,而是替代XML的主力方案,但有硬约束:
OPENJSON():将JSON数组解析为行集,默认不走索引,全表扫描。当JSON字段存储大量数据时,必须配合CROSS APPLY和WHERE过滤:-- ❌ 危险:先OPENJSON再WHERE,解析全部JSON SELECT * FROM Orders CROSS APPLY OPENJSON(ExtraInfo) WHERE value LIKE '%urgent%'; -- ✅ 安全:先WHERE筛选主表,再OPENJSON SELECT * FROM Orders WHERE ExtraInfo LIKE '%"priority":"urgent"%' -- 利用全文索引或LIKE快速过滤 CROSS APPLY OPENJSON(ExtraInfo);JSON_VALUE():提取标量值,可被查询优化器下推到索引查找(需开启JSON列的计算列索引):-- 创建持久化计算列并索引(关键!) ALTER TABLE Orders ADD PriorityLevel AS JSON_VALUE(ExtraInfo, '$.priority') PERSISTED; CREATE INDEX IX_Orders_PriorityLevel ON Orders(PriorityLevel); -- 查询自动走索引 SELECT * FROM Orders WHERE PriorityLevel = 'high';
3. 按执行模型分层:标量函数、内联表值函数、多语句表值函数的性能生死线
函数类型决定执行计划命运。很多性能问题根源不在SQL写法,而在函数选型错误。我按实际执行开销,把函数分为三层——不是理论分类,而是执行计划中是否生成嵌套循环、是否阻塞并行、是否产生额外IO的实战分层。
3.1 标量函数(Scalar UDF):SQL Server的“性能毒丸”,何时必须用、如何绕开
标量函数(如CREATE FUNCTION dbo.CalculateTax(@amt DECIMAL) RETURNS DECIMAL)在SQL Server 2019前是单行逐行执行、无法并行、阻塞SARG的代名词。即使函数体只有RETURN @amt * 0.08,也会让整个查询变慢10倍以上。
- 必须用的场景:业务规则强耦合(如特定行业税率计算)、加密解密(
ENCRYPTBYPASSPHRASE)、或兼容旧系统无法重构。 - 绕开方案:用内联表值函数(ITVF)替代。ITVF本质是参数化视图,可内联展开,享受查询优化器全能力:
-- ❌ 标量函数:性能灾难 CREATE FUNCTION dbo.GetTaxScalar(@amt DECIMAL(18,2)) RETURNS DECIMAL(18,2) AS BEGIN RETURN @amt * 0.08; END; -- ✅ ITVF:编译时内联,无性能损失 CREATE FUNCTION dbo.GetTaxInline(@amt DECIMAL(18,2)) RETURNS TABLE AS RETURN SELECT @amt * 0.08 AS TaxAmount; -- 使用方式(语法一致,性能天壤之别) SELECT o.OrderID, t.TaxAmount FROM Orders o CROSS APPLY dbo.GetTaxInline(o.Total) t;
3.2 内联表值函数(ITVF):参数化视图的终极形态,索引友好型
ITVF是SQL Server函数体系中的“优等生”。它没有函数体,只有RETURN SELECT...,因此:
- 可被查询优化器完全展开,参与连接、筛选、索引选择;
- 支持统计信息,基数估算准确;
- 无额外执行计划节点,零开销。
典型用法:构建可复用的业务逻辑单元:
-- 安全的客户等级计算(含NULL处理) CREATE FUNCTION dbo.GetCustomerTier(@customerID INT) RETURNS TABLE AS RETURN SELECT CASE WHEN SUM(o.Total) >= 100000 THEN 'VIP' WHEN SUM(o.Total) >= 50000 THEN 'Gold' ELSE 'Standard' END AS Tier FROM Customers c LEFT JOIN Orders o ON c.CustomerID = o.CustomerID AND o.Status = 'Shipped' WHERE c.CustomerID = @customerID GROUP BY c.CustomerID; -- 在复杂查询中无缝集成 SELECT c.Name, t.Tier, COUNT(o.OrderID) AS OrderCount FROM Customers c CROSS APPLY dbo.GetCustomerTier(c.CustomerID) t LEFT JOIN Orders o ON c.CustomerID = o.CustomerID GROUP BY c.Name, t.Tier;3.3 多语句表值函数(MSTVF):黑匣子陷阱,仅用于不可拆解的复杂逻辑
MSTVF(BEGIN ... INSERT INTO @table ... RETURN @table)是执行计划中的黑匣子。优化器对其内部逻辑一无所知,总是假设返回1行,导致严重基数估算错误。仅在以下场景考虑:
- 必须使用游标或临时表的遗留算法;
- 需要多次
INSERT/UPDATE的复合逻辑(如递归路径生成); - 与外部系统交互(如调用
xp_cmdshell,虽不推荐)。
即便如此,也需强制指定WITH SCHEMABINDING和RETURNS TABLE的精确结构,并在调用处用OPTION (RECOMPILE)缓解估算偏差:
-- MSTVF示例(仅作说明,生产慎用) CREATE FUNCTION dbo.GeneratePath(@rootID INT) RETURNS @path TABLE (NodeID INT, Level INT, PathStr VARCHAR(500)) WITH SCHEMABINDING AS BEGIN DECLARE @level INT = 0; INSERT INTO @path VALUES (@rootID, @level, CAST(@rootID AS VARCHAR)); WHILE @@ROWCOUNT > 0 BEGIN SET @level = @level + 1; INSERT INTO @path SELECT c.ChildID, @level, p.PathStr + '->' + CAST(c.ChildID AS VARCHAR) FROM @path p INNER JOIN Hierarchy c ON p.NodeID = c.ParentID WHERE p.Level = @level - 1; END RETURN; END; -- 调用时强制重编译,避免基数误估 SELECT * FROM dbo.GeneratePath(1) OPTION (RECOMPILE);4. 按版本演进划界:SQL Server 2012/2016/2017/2022函数能力断崖与迁移避坑指南
SQL Server函数不是静态清单,而是随版本剧烈演进的活体。忽略版本差异,等于在生产环境埋雷。我按函数首次引入版本、关键行为变更、废弃警告三维度,划出四条生存线。
4.1 SQL Server 2012:LEAD()/LAG()开启分析函数时代,但FORMAT()是伪命题
2012是分析函数元年,但FORMAT()函数在此版本存在致命缺陷:
- 仅支持.NET Framework文化标识(如
'en-US'),不支持SQL Server原生语言ID(如1033); - 性能极差:每次调用触发CLR,比
CONVERT()慢20倍以上; - 生产禁用:我团队明文规定,2012环境禁止使用
FORMAT(),统一用CONVERT()+RIGHT()组合:-- ❌ 2012中FORMAT():慢且不稳定 SELECT FORMAT(GETDATE(), 'yyyy-MM-dd HH:mm:ss'); -- ✅ 安全替代(兼容所有版本) SELECT CONVERT(VARCHAR, GETDATE(), 120) AS StandardISO;
4.2 SQL Server 2016:JSON原生支持落地,但STRING_SPLIT()无序是硬伤
2016带来JSON曙光,但STRING_SPLIT()函数设计反直觉:
- 返回结果无序:
SELECT value FROM STRING_SPLIT('a,b,c', ',')不保证a/b/c顺序; - 无序导致
CROSS APPLY连接失效:若需按位置匹配(如col1=split1, col2=split2),必须用ROW_NUMBER()强行编号:-- ❌ 错误:顺序不可靠 SELECT t.ID, s.value AS Tag FROM Tags t CROSS APPLY STRING_SPLIT(t.TagList, ',') s; -- ✅ 正确:添加序号,再JOIN SELECT t.ID, s.value AS Tag, s.ordinal AS Position FROM Tags t CROSS APPLY ( SELECT value, ordinal FROM STRING_SPLIT(t.TagList, ',') WITH ORDINALITY ) s;
4.3 SQL Server 2017:TRIM()/STRING_AGG()上线,但TRIM()不处理Unicode空白
2017的TRIM()是重大进步,但文档未强调其局限:
- 仅识别ASCII空白字符(U+0000-U+0020),对中文全角空格(U+3000)、不间断空格(U+00A0)完全无效;
- 解决方案:必须预处理Unicode空白。我封装了
dbo.TrimUnicode():CREATE FUNCTION dbo.TrimUnicode(@input NVARCHAR(MAX)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @result NVARCHAR(MAX) = @input; -- 移除常见Unicode空白 SET @result = REPLACE(@result, NCHAR(160), N''); -- 不间断空格 SET @result = REPLACE(@result, NCHAR(8194), N''); -- 细空格 SET @result = REPLACE(@result, NCHAR(8195), N''); -- 中空格 RETURN LTRIM(RTRIM(@result)); END;
4.4 SQL Server 2022:GENERATE_SERIES()终结循环噩梦,但JSON_PATH_EXISTS()需谨慎
2022的GENERATE_SERIES()是ETL开发者的福音:
- 替代WHILE循环:生成数字序列、日期序列不再需要临时表或CTE递归;
- 性能碾压:100万行生成仅需毫秒级。
-- ✅ 生成2023全年日期(无需递归CTE) SELECT DATEADD(DAY, value, '2023-01-01') AS DateKey FROM GENERATE_SERIES(0, 364); JSON_PATH_EXISTS():检查JSON路径是否存在,但路径表达式语法严格:$.store.book[0].title有效,$.store.book[*].title(通配符)在2022中不支持,需用OPENJSON()替代。
5. 函数避坑:生产环境血泪总结的7个高频翻车点与根因定位法
这些坑我都亲手踩过,轻则查询慢10倍,重则数据错乱。每一条都附带现象→原因→解决的完整链路,拒绝模糊描述。
5.1 现象:LEN()在NVARCHAR字段上返回值比预期小一半
原因:LEN()返回字符数,而非字节数;NVARCHAR中每个字符占2字节,但LEN()只计“逻辑字符”。当字段存的是N'Hello'(5字符)和N'你好'(2字符),LEN()都返回字符数,与字节无关。
解决:明确区分用途——判断内容长度用LEN(),判断存储空间用DATALENGTH();清洗时用DATALENGTH(LTRIM(RTRIM(@str)))判断是否真为空。
5.2 现象:ISNULL()和COALESCE()返回类型不一致,导致隐式转换失败
原因:ISNULL()返回第一个参数类型,COALESCE()返回参数中最高优先级类型。ISNULL(1, 'abc')返回INT(报错),COALESCE(1, 'abc')返回VARCHAR(成功但有隐式转换开销)。
解决:统一用COALESCE(),并在所有参数显式CAST为同一类型:COALESCE(CAST(@val AS VARCHAR(10)), 'N/A')。
5.3 现象:GETDATE()在分布式事务中返回不同服务器时间,导致数据不一致
原因:GETDATE()依赖本地系统时钟,跨服务器无同步机制;分布式事务中各节点时间差>1秒即可能违反业务约束。
解决:全局统一用GETUTCDATE(),并在应用层注入UTC时间戳作为事务基准;数据库层禁用GETDATE()。
5.4 现象:STRING_AGG()在大数据集上内存溢出(Error 701)
原因:STRING_AGG()默认在内存中构建字符串,超限触发OOM;尤其当聚合字段含长文本时。
解决:启用MAXDOP 1限制并行,或分批聚合:SELECT STRING_AGG(value, ',') FROM (SELECT TOP 10000 value FROM table ORDER BY id) t。
5.5 现象:JSON_VALUE()查询不走索引,执行计划显示Table Scan
原因:未创建持久化计算列索引;JSON_VALUE()本身可下推,但需显式建立计算列并索引。
解决:ALTER TABLE ADD col AS JSON_VALUE(json_col, '$.path') PERSISTED; CREATE INDEX IX ON table(col);。
5.6 现象:TRY_CAST()在SQL Server 2014中对DATETIME2返回NULL,但文档未标注
原因:TRY_CAST()对DATETIME2的支持始于SQL Server 2016,2014中该类型转换始终失败。
解决:升级前全面扫描代码,将TRY_CAST(@str AS DATETIME2)替换为TRY_CONVERT(DATETIME2, @str)(2012+均支持)。
5.7 现象:标量UDF在查询中导致并行计划退化为串行
原因:SQL Server 2019前,标量UDF强制串行执行,无论MAXDOP设置如何。
解决:立即重构为ITVF;若无法重构,启用QUERYTRACEON 11024(2019+)或SCHEMABINDING减少开销。
6. 进阶技巧:用函数构建可验证、可审计、可回滚的业务逻辑中枢
函数不该是孤立的代码块,而应成为业务规则的“可执行契约”。我最后分享三个已在金融核心系统验证的实战技巧,它们让函数从工具升维为治理载体。
6.1 技巧一:用SCHEMABINDING+EXECUTE AS OWNER实现函数权限最小化
函数常被滥用为权限后门。通过绑定架构和执行上下文,可彻底隔离风险:
-- 创建函数时强制绑定,防止底层表被删 CREATE FUNCTION dbo.CalculateRiskScore(@loanAmt DECIMAL) RETURNS DECIMAL(5,2) WITH SCHEMABINDING -- 绑定到引用的表,表不能被删 AS BEGIN RETURN (SELECT AVG(r.Rating) FROM dbo.RiskFactors r); END; -- 授予执行权,但不授予底层表SELECT权 GRANT EXECUTE ON dbo.CalculateRiskScore TO [AppRole]; -- 底层RiskFactors表对AppRole不可见效果:应用角色只能调用函数获取分数,无法直接查
RiskFactors表窃取风控模型细节。
6.2 技巧二:用sys.dm_exec_function_stats监控函数真实开销
不要信文档的“O(1)”,用DMV看真实世界表现:
-- 查看所有函数的执行统计(需启用Query Store或手动收集) SELECT OBJECT_NAME(object_id) AS FunctionName, execution_count, total_logical_reads / execution_count AS avg_logical_reads, total_elapsed_time / execution_count AS avg_elapsed_ms, last_execution_time FROM sys.dm_exec_function_stats WHERE database_id = DB_ID() ORDER BY total_elapsed_time DESC;我曾用此发现某dbo.ParseAddress()函数平均耗时80ms,占查询总耗时60%,立即用正则替换为STRING_SPLIT()+PIVOT重构,降为3ms。
6.3 技巧三:用版本化函数实现业务规则热切换
业务规则常需灰度发布。通过函数名版本号,实现零停机切换:
-- v1:旧规则(保留供回滚) CREATE FUNCTION dbo.CalculateTax_v1(@amt DECIMAL) ... -- v2:新规则(默认使用) CREATE FUNCTION dbo.CalculateTax_v2(@amt DECIMAL) ... -- 创建同名函数指向v2(应用无感知) CREATE FUNCTION dbo.CalculateTax(@amt DECIMAL) RETURNS DECIMAL AS BEGIN RETURN dbo.CalculateTax_v2(@amt); END; -- 回滚只需改一行 ALTER FUNCTION dbo.CalculateTax(...) AS BEGIN RETURN dbo.CalculateTax_v1(@amt); END;血泪经验:在支付系统上线新税率规则时,这套机制让我们在30秒内完成回滚,避免了千万级资损。函数不是一次写完就扔的代码,而是业务契约的活体载体——你调用它的每一行,都在为数据质量签字画押。我至今保留一个习惯:写完函数必做三件事——查
sys.dm_exec_function_stats看开销、用SET STATISTICS XML ON看执行计划、在测试库用DBCC FREEPROCCACHE清缓存后压测。希望帮到你。
本文还有配套的精品资源,点击获取