简介这份《SQL Server函数大全精析》面向数据库开发、运维人员及备考SQL相关认证的学习者系统梳理T-SQL函数体系帮助解决函数分类不清、确定性与非确定性混淆、变量传参不熟等常见问题。资源包内含1个doc文档约859KB以文字讲解与代码示例为主便于离线查阅和逐节研读。内容按聚合、配置、转换、加密、游标、日期时间、数学、元数据、排名、行集、安全、字符串、系统、系统统计、文本图像等类别展开并重点剖析确定性函数如AVG、CAST、CONVERT、DATEADD、DATEDIFF、ASCII、CHAR、SUBSTRING与非确定性函数如GETDATE、RAND、ERROR、CURSOR_STATUS的差异同时演示用SET与SELECT为变量赋值、将变量传入SQRT等函数的写法。目前已有1190人学习适合希望夯实函数基础、优化查询与存储过程编写的读者参考。1. 从一次线上事故说起为什么我又把 SQL Server 函数翻了出来上周排查一个对账问题存储过程里嵌套了三层ISNULL加CAST结果金额字段在某个边界值上被静默截断业务侧对不上账查了大半天才定位到是一个ROUND的精度参数写反了。这种事不是第一次了——SQL Server 内置函数看着简单真到生产环境里字符串截取、日期换算、类型转换、空值处理这几类函数一旦用错参数或忽略排序规则翻车往往悄无声息。这份「SQL Server 函数大全精析」就是冲着这个痛点整理的它不是把联机丛书抄一遍而是按字符串、日期时间、数学、转换、聚合、窗口、系统元数据这几条线把每个常用函数的签名、参数含义、返回值边界和典型误用场景拆开讲。适合已经能写基本 T-SQL、但在复杂查询和存储过程里经常被函数细节绊住的开发者和 DBA。下面我按自己拆包和复现的节奏把这份资料怎么用、哪些地方值得反复看、哪些坑必须提前知道一条条说清楚。2. 字符串与日期函数参数表背后的排序规则和精度陷阱字符串和日期时间函数是日常写得最多、也最容易想当然的两类。这份资料在这两块给的不是简单罗列而是把每个函数的参数逐个拆开尤其是那些带可选参数、受排序规则影响的函数讲得比官方文档更贴近实际写代码时的决策过程。2.1 字符串函数LEN 和 DATALENGTH 到底差在哪很多人第一次被字符串函数坑就是LEN和DATALENGTH混用。资料里用一张对照表把这两个函数的差异讲透了我照着复现了一遍确实比死记结论管用。函数作用是否忽略尾随空格返回类型典型场景LEN返回字符数是int校验用户输入长度DATALENGTH返回字节数否int判断实际存储占用CHARINDEX查找子串位置不适用int解析分隔字符串SUBSTRING截取子串不适用变长拆分拼接字段关键点在于LEN会忽略尾随空格而DATALENGTH不会。如果你的字段是CHAR(10)存了abcLEN返回 3DATALENGTH返回 10。资料里特别提醒用LEN做长度校验时如果业务上不允许尾随空格得配合DATALENGTH或先RTRIM再判断否则用户输入abc 会被当成合法。另一个高频坑是CHARINDEX和PATINDEX的选择。CHARINDEX只能找固定子串PATINDEX支持通配符。资料里给了一个解析逗号分隔字符串的示例我把它改成了自己项目里的标签解析逻辑-- 解析形如 a,b,c 的标签串返回第 n 个标签 DECLARE tags NVARCHAR(200) N数据库,性能优化,索引; DECLARE pos INT 1, n INT 2, idx INT 1; WHILE idx n BEGIN -- 找下一个逗号位置从当前位置之后开始 SET pos CHARINDEX(N,, tags, pos) 1; SET idx idx 1; END -- 找当前标签的结束位置 DECLARE end INT CHARINDEX(N,, tags, pos); IF end 0 SET end LEN(tags) 1; SELECT SUBSTRING(tags, pos, end - pos) AS Tag;这段逻辑说明CHARINDEX的第三个参数是起始搜索位置循环里每次把pos往后推避免死循环。参数上要注意pos初始为 1CHARINDEX找不到时返回 0所以end 0要单独处理成字符串末尾。资料里还提醒如果标签串里可能有连续逗号或首尾逗号得先做清洗否则会截出空串。2.2 日期时间函数DATEADD 和 DATEDIFF 的边界行为日期函数这块资料把DATEADD、DATEDIFF、DATEPART、EOMONTH几个核心函数的参数和返回值边界列得很细。我重点看了DATEDIFF的跨边界行为——它统计的是“边界跨越次数”不是精确时间差。比如-- 计算两个时间点跨越了多少个“天”边界 SELECT DATEDIFF(DAY, 2024-01-01 23:59:59, 2024-01-02 00:00:01) AS DayDiff; -- 返回 1尽管实际只差 2 秒这个行为在做“按天分组统计”时是符合预期的但如果你拿它算“实际间隔天数”就会出错。资料里建议需要精确间隔时用DATEDIFF_BIG配合秒级单位再换算或者直接用时间戳相减。DATEADD的坑在于月份加减给 1 月 31 日加一个月结果是 2 月 28 日非闰年不会报错也不会自动跳到 3 月。资料里用表格列了各日期部分year、quarter、month、dayofyear、weekday 等的缩写和边界其中weekday和dw在设置DATEFIRST后行为会变跨时区或跨语言环境部署时要特别小心。提示日期函数的结果受SET DATEFORMAT和SET LANGUAGE影响存储过程里如果依赖字符串转日期最好显式用CONVERT指定样式代码别赌服务器默认设置。3. 转换与空值处理CAST、CONVERT、TRY_CAST 的选型逻辑类型转换和空值处理是存储过程里翻车最密集的区域。这份资料没有停留在“CAST 和 CONVERT 都能转”这种层面而是把样式代码、隐式转换规则、TRY_ 系列函数的适用边界讲清楚了这部分我反复看了几遍。3.1 CAST 与 CONVERT样式代码决定输出格式CAST和CONVERT功能重叠但CONVERT多了一个样式参数专门用于日期和货币的格式化输出。资料里给了一张常用样式代码表我摘几个实际项目里高频的样式代码格式示例输出112yyyymmdd20240115120yyyy-mm-dd hh:mi:ss2024-01-15 08:30:00126ISO86012024-01-15T08:30:00.000101mm/dd/yyyy01/15/2024选型逻辑很简单如果只是类型转换、不关心格式用CAST更简洁如果要控制日期输出格式用CONVERT加样式代码。资料里特别提醒样式代码 126 是 ISO8601 格式适合做跨系统数据交换但要注意它带毫秒如果目标字段是datetime且精度不够会四舍五入。隐式转换是另一个大坑。资料里举了个例子WHERE varchar_col 123会导致列上的索引失效因为 SQL Server 把varchar_col隐式转成了int。正确写法是WHERE varchar_col 123。这个点我在多个项目里都遇到过资料把它放在转换章节开头讲位置很对。3.2 TRY_CAST 与 TRY_CONVERT什么时候该用“安全转换”从 SQL Server 2012 开始引入的TRY_CAST和TRY_CONVERT在转换失败时返回NULL而不是报错。资料里给了一个典型场景从外部导入的 CSV 数据里金额字段可能混有非数字字符直接用CAST会让整个批处理中断用TRY_CAST则可以把异常行标记出来单独处理。-- 安全转换转换失败返回 NULL不中断查询 SELECT RawValue, TRY_CAST(RawValue AS DECIMAL(18,2)) AS SafeAmount, CASE WHEN TRY_CAST(RawValue AS DECIMAL(18,2)) IS NULL THEN 格式异常 ELSE 正常 END AS Status FROM StagingTable;逻辑说明TRY_CAST在遇到abc、12.3.4这类无法转换的值时返回NULL配合CASE就能把脏数据筛出来。参数上要注意TRY_CAST的目标类型精度要足够如果目标类型是DECIMAL(5,2)而源值超过 999.99会返回NULL而不是截断。资料里还提醒TRY_CAST不能替代数据校验它只是把报错变成了NULL业务上仍需决定这些NULL怎么处理。注意TRY_CAST和TRY_CONVERT在转换失败时返回NULL但如果源值本身就是NULL也返回NULL两者无法区分。需要区分时先用ISNULL或CASE判断源值。4. 聚合与窗口函数OVER 子句的分区、排序和帧边界聚合函数和窗口函数是这份资料里篇幅最重的部分之一。普通聚合SUM、COUNT、AVG大家都会用但加上OVER子句之后分区、排序、帧边界三个参数组合起来行为差异很大。资料把这块拆成了“聚合函数 OVER”“排名函数”“分析函数”三条线来讲。4.1 窗口聚合ROWS 和 RANGE 的区别OVER子句里如果不写ROWS或RANGE默认帧是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这个默认行为在遇到重复排序值时会把所有相同值的行都算进当前帧导致累计结果和预期不符。资料里给了一个对比示例-- 默认 RANGE相同日期的行会互相影响累计值 SELECT OrderDate, Amount, SUM(Amount) OVER (ORDER BY OrderDate) AS RunningTotal_Range FROM Orders; -- 显式 ROWS严格按行序累计相同日期也逐行累加 SELECT OrderDate, Amount, SUM(Amount) OVER (ORDER BY OrderDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal_Rows FROM Orders;逻辑说明当OrderDate有重复值时RANGE会把同一天的所有行视为同一帧累计值会“跳变”ROWS则严格按行号逐行累加。参数上ROWS后面跟的是物理行偏移RANGE跟的是逻辑值偏移。资料建议做逐行累计时一律显式写ROWS避免默认RANGE带来的玄学差异。4.2 排名函数ROW_NUMBER、RANK、DENSE_RANK 的选型这三个函数在分页、去重、Top-N 场景里高频出现但返回值规则不同。资料用一张表把差异讲清楚了函数相同值处理后续排名典型场景ROW_NUMBER强制唯一连续分页、去重取一条RANK相同值同名次跳号成绩排名允许并列DENSE_RANK相同值同名次不跳号等级划分选型逻辑分页用ROW_NUMBER因为需要唯一行号排名用RANK或DENSE_RANK看业务是否允许跳号。资料里提醒ROW_NUMBER的ORDER BY如果不唯一每次执行结果可能不同分页时会导致数据重复或丢失。解决办法是在ORDER BY里加上唯一键做 tie-breaker。-- 分页查询ORDER BY 加唯一键保证稳定 SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY CreateTime DESC, Id ASC) AS RowNum FROM Articles ) t WHERE t.RowNum BETWEEN 21 AND 40;参数说明CreateTime DESC是主排序Id ASC是 tie-breaker确保相同CreateTime的行也有确定顺序。资料里还提到SQL Server 2012 之后可以用OFFSET FETCH替代ROW_NUMBER分页语法更简洁但底层执行计划类似。5. 避坑与排查函数使用中最容易翻车的五个点这一章是我觉得这份资料最有价值的部分。它没有泛泛讲“注意性能”而是把几个具体到函数级别的坑列出来每个都按“现象 → 原因 → 解决”写清楚。我结合自己踩过的挑五个最典型的。5.1 坑一ISNULL 和 COALESCE 的返回类型不一致现象ISNULL(Amount, 0)在Amount是DECIMAL(18,2)时返回DECIMAL(18,2)但COALESCE(Amount, 0)可能返回INT导致后续计算精度丢失。原因ISNULL的返回类型取第一个参数的类型COALESCE取所有参数中优先级最高的类型。0是INT优先级高于DECIMAL时就会改变结果类型。解决用COALESCE时把默认值写成同类型比如COALESCE(Amount, 0.00)或者统一用ISNULL。5.2 坑二GETDATE() 在函数或视图里导致执行计划无法复用现象视图里用了GETDATE()做过滤每次查询都走全表扫描索引失效。原因GETDATE()是非确定性函数SQL Server 无法在编译时估算选择性只能给一个固定估算值。解决把GETDATE()的值先赋给变量再用变量过滤或者用SYSDATETIME()配合OPTION (RECOMPILE)但后者有编译开销。5.3 坑三SUBSTRING 的起始位置为 0 或负数现象SUBSTRING(abcdef, 0, 3)返回ab而不是报错容易在循环截取时多截或少截。原因SQL Server 的SUBSTRING起始位置为 0 时实际从第 1 个字符开始但长度会减 1。解决截取前用CASE保证起始位置 ≥ 1或者用STUFF替代。5.4 坑四COUNT(*) 和 COUNT(列) 在含 NULL 时的差异现象统计行数时用COUNT(Status)结果比实际行数少因为Status为NULL的行没被计入。原因COUNT(列)忽略NULLCOUNT(*)统计所有行。解决统计行数一律用COUNT(*)统计非空值数量才用COUNT(列)。5.5 坑五字符串拼接用 遇到 NULL 整个变 NULL现象SELECT FirstName LastName当LastName为NULL时整个结果变NULL。原因SQL Server 中NULL 任何值 NULL。解决用CONCAT函数自动把NULL当空串或者用ISNULL包裹每个字段。提示CONCAT从 SQL Server 2012 开始支持如果版本更低只能用ISNULL逐个处理。6. 进阶技巧用系统函数做元数据查询和性能自查资料最后一章讲的是系统函数和元数据函数这部分平时用得少但排查问题时很管用。我挑两个自己常用的场景说。第一个是查当前数据库里所有表的行数和空间占用用sys.dm_db_partition_stats配合OBJECT_NAME-- 查各表行数和保留空间KB SELECT OBJECT_NAME(p.object_id) AS TableName, SUM(p.row_count) AS RowCounts, SUM(p.reserved_page_count) * 8 AS ReservedKB FROM sys.dm_db_partition_stats p JOIN sys.tables t ON p.object_id t.object_id WHERE p.index_id IN (0, 1) -- 0堆1聚集索引 GROUP BY p.object_id ORDER BY ReservedKB DESC;逻辑说明index_id为 0 表示堆表1 表示聚集索引只取这两个避免重复计算非聚集索引。reserved_page_count单位是页乘 8 得到 KB。这个查询比sp_spaceused更灵活可以一次看所有表。第二个是查当前会话的等待类型用sys.dm_exec_requests和sys.dm_os_wait_stats关联快速定位阻塞源。资料里给了模板我改成自己常用的版本-- 查当前活跃请求的等待类型和阻塞会话 SELECT r.session_id, r.blocking_session_id, r.wait_type, r.wait_time, r.status, SUBSTRING(t.text, r.statement_start_offset/2 1, (CASE WHEN r.statement_end_offset -1 THEN LEN(CONVERT(NVARCHAR(MAX), t.text)) * 2 ELSE r.statement_end_offset END - r.statement_start_offset)/2 1) AS CurrentStatement FROM sys.dm_exec_requests r CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t WHERE r.session_id 50; -- 排除系统会话参数说明statement_start_offset和statement_end_offset是字节偏移除以 2 得到字符位置。blocking_session_id不为 0 时说明被阻塞顺着这个 ID 可以找到源头会话。资料里提醒这个查询需要VIEW SERVER STATE权限普通账号可能看不到。从那以后我每次写完带函数的存储过程都会强制走一遍TRY_CAST包裹外部输入、显式写ROWS做窗口累计、用CONCAT替代拼字符串这三步省了不少回头查问题的时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?