聊到SQL Server,绝大多数人最先想搞明白的就是日期转换、字符串处理、数学计算和聚合统计这四类常用函数。我做了这么多年数据相关的工作,凡是写报表、做数据分析、维护数据库后台,翻来覆去用的也就是这些功能。这篇就把SQL Server里最常用的函数整理成一份能直接抄作业的清单,每个函数都配上说明和示例,遇到日期转字符串、字符串转数字、分组汇总这类需求,直接照着写就行。
不管是刚接触SQL的新手,还是已经写了几年存储过程的开发,这份内容都能让你少走弯路。我会把那些容易被忽略的细节、经常踩的坑一块讲清楚,比如LEN到底计不计空格、COUNT(*)和COUNT(列)差在哪、ROUND的第三个参数是干嘛的、GROUP BY和HAVING的执行顺序等等。看的时候建议顺手开个SQL Server Management Studio(SSMS),跟着敲一遍,记忆会扎实很多。
1. 先弄明白:这些函数到底解决什么问题
1.1 这些函数在真实项目里长什么样
很多人学函数是一股脑背语法,背完就忘。实际上SQL函数不是靠背的,是靠"需求驱动"记住的。你真正写报表的时候,会遇到的问题无非这么几类:订单时间要转成"2024-05-01"这种格式、客户姓名要去掉首尾空格、价格要向上取整、各部门销售总额要汇总对比、某个月的销售额环比增长率要算出来。
我举个例子,一张订单表Orders,有订单日期OrderDate、客户ID CustomerID、金额Amount。最常见的统计需求就是"2024年每个月每个部门的销售总额、订单数、平均客单价"。拆开看,它就是组合使用:
- CONVERT把OrderDate转成月份标识;
- SUM、COUNT、AVG做聚合;
- GROUP BY按部门和月份分组;
- DATEDIFF或DATEPART算时间维度。
这些函数单独看都不难,难的是组合能力和对细节的把控。所以这篇不只是列函数清单,更会解释每个函数背后的行为和容易出错的地方。明白这些,你遇到问题的时候才会知道"该用哪个、为什么用这个、用了之后可能踩什么坑"。
1.2 我建议的学法和查法
学习SQL Server函数,最忌讳的就是拿起一本书从头念到尾。我的经验是:先建一个临时表,塞入几行测试数据,把每个函数跑一遍,再看结果是否符合预期。比如建一个只有几行的订单表,分别验证SUM、AVG、COUNT、字符串拼接、日期加减,效果比看一百遍语法都好。
另外,把常用函数整理成一张速查表放到手边。这篇内容你完全可以当成速查手册用,看到某个函数想不起来具体语法,直接搜标题或者关键词就能定位。我平时写SQL也是查得多了才有肌肉记忆,别指望一次记住所有东西。
2. 日期转换与日期时间函数:格式化、加减、区间计算
2.1 先拿当前日期:GETDATE 和 SYSDATETIME
做报表几乎每次都要取当前时间。SQL Server里最基本的两个函数是GETDATE()和SYSDATETIME()。
SELECT GETDATE(); -- 返回当前日期和时间,精度到毫秒 SELECT SYSDATETIME(); -- 返回当前日期和时间,精度到100纳秒如果只是需要当前日期(不要时间),可以用:
SELECT CAST(GETDATE() AS DATE);注意CAST需要写完整,它的作用是去掉时间部分,只保留年月日。这在报表日期条件里非常常用,比如查今天所有的订单:
SELECT * FROM Orders WHERE OrderDate >= CAST(GETDATE() AS DATE);这个写法比拿OrderDate和GETDATE()直接比较要可靠,因为它不会漏掉当天的记录。
2.2 日期转字符串:CONVERT 样式号别硬背
SQL Server把日期转字符串,最常用的不是CAST,而是CONVERT。CONVERT的语法是 CONVERT(目标类型, 表达式, 样式号)。这里样式号就是很多人记不住的地方,其实只用记常用的几个就够了。
SELECT CONVERT(varchar(10), GETDATE(), 101); -- MM/dd/yyyy SELECT CONVERT(varchar(10), GETDATE(), 103); -- dd/MM/yyyy SELECT CONVERT(varchar(10), GETDATE(), 112); -- yyyyMMdd SELECT CONVERT(varchar(23), GETDATE(), 120); -- yyyy-MM-dd HH:mm:ss SELECT CONVERT(varchar(23), GETDATE(), 121); -- yyyy-MM-dd HH:mm:ss.mmm SELECT CONVERT(varchar(10), GETDATE(), 23); -- yyyy-MM-dd日常工作中,112和120是我用得最多的。112适合做排序,因为年月日数值大小就代表日期的先后;120适合导出给别的系统,中间有横杠和冒号,别人一眼能看懂。101和103的区别要特别注意,101是月在前(美国习惯),103是日在先(欧洲习惯),如果不小心把用户传过来的日期当成另一种格式解析,非常容易出错。
还有个常见需求是日期转成中文格式,SQL Server没有直接的"yyyy年MM月dd日"内置样式,但可以用CONVERT加字符串拼接实现:
SELECT CONVERT(varchar(4), DATEPART(year, GETDATE())) + '年' + CONVERT(varchar(2), DATEPART(month, GETDATE())) + '月' + CONVERT(varchar(2), DATEPART(day, GETDATE())) + '日';这种写法在生成中文报表标题时很常见。
2.3 字符串转日期:三种写法与坑
把字符串转成日期,有的朋友一上来就写CAST('2024-05-01' AS DATE),大多数情况没问题,可一旦遇到'20240501'这种格式,CAST直接报错或者返回奇怪的结果。稳妥的做法是用CONVERT并指定样式号。
-- 推荐写法 SELECT CONVERT(DATE, '20240501', 112); SELECT CONVERT(DATETIME, '2024-05-01 12:30:00', 120); -- 不推荐但常见的写法 SELECT CAST('2024-05-01' AS DATE);如果文本来源不可控,混进了'abc'之类的内容,SQL Server 2012以后可以用TRY_CONVERT和TRY_CAST。它们转换失败时不会报错,而是返回NULL,让程序走下一步处理:
SELECT TRY_CONVERT(DATE, '2024-13-01', 112); -- 返回NULL,因为13月不合法 SELECT TRY_CAST('abc' AS INT); -- 返回NULL这个特性在写批量导入和清洗脏数据时非常好用,能保住整个查询不被一条坏数据拖垮。
2.4 DATEADD、DATEDIFF、DATEPART:日期加减和区间计算
DATEADD用于给日期加时间单位,DATEDIFF用于计算两个日期的间隔,DATEPART用于取日期的某一部分(年、月、日、周几等)。三个都是高频函数,但各有各的细节坑。
-- 加7天、减3个月 SELECT DATEADD(day, 7, GETDATE()); SELECT DATEADD(month, -3, GETDATE()); -- 两个日期相差多少天 SELECT DATEDIFF(day, '20240101', '20240110'); -- 9 -- 取当前日期的年份、月份 SELECT DATEPART(year, GETDATE()); SELECT DATEPART(month, GETDATE());DATEDIFF有个特别容易踩的坑:它计算的是两个日期之间边界跨越的次数,不是精确的时间间隔。比如DATEDIFF(year, '20231231', '20240101')返回1,因为年份边界从2023跨到了2024,但两个日期实际只差一天。如果你要计算精确的相差天数、小时数,通常没问题;但跨年、跨月、跨周的统计一定要想清楚需求。
另外DATEPART(weekday, GETDATE())返回的是1到7,但数字代表星期几跟当前会话的DATEFIRST设置有关。默认情况下SQL Server认为周日是1,如果想按周一作为一周的开始,先执行:
SET DATEFIRST 1;否则你在统计"周一订单量"时,很容易把周日也算进去。
再说一个经典写法:求本月第一天。大多数人的第一反应是拼字符串,其实有更优雅的写法:
SELECT DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0);这个式子的原理是:0代表基准日期1900-01-01,先把当前日期和基准日期之间跨越的月数算出来,再把这么多个“整月”加回基准日期,得到的一定是本月1号。同理,求上月末可以先用EOMONTH函数:
SELECT EOMONTH(GETDATE()); -- 本月最后一天 SELECT DATEADD(day, 1, EOMONTH(GETDATE())); -- 下月第一天EOMONTH是SQL Server 2012加入的函数,做月末统计时比手工算日期方便太多。
2.5 FORMAT 更灵活但慢
SQL Server 2012以后有个FORMAT函数,可以直接用.NET的格式写法转日期:
SELECT FORMAT(GETDATE(), 'yyyy-MM-dd'); SELECT FORMAT(GETDATE(), 'yyyy年MM月dd日');FORMAT在灵活性上确实很好,但性能比CONVERT差不少。因为它底层要调用.NET的CLR方法,每行数据都要做一次格式化。如果只是对单条数据做格式化没关系,但用在百万行的大表查询里,速度差距会很明显。我的习惯是:导出少量数据或者做界面展示时用FORMAT,大数据量加工时一律用CONVERT。
另外FORMAT的格式串区分大小写,月份用大写MM,分钟用小写mm,年份用yyyy(小写)。写错了不会报错,但可能输出一串莫名其妙的原文,排查起来非常费劲。
3. 字符串函数:截取、拼接、替换、拆分全套实操
3.1 长度与截取:LEN、LEFT、RIGHT、SUBSTRING
字符串处理里最常用的就是取长度和截取。
SELECT LEN('Hello World'); -- 11 SELECT LEFT('Hello World', 5); -- Hello SELECT RIGHT('Hello World', 5); -- World SELECT SUBSTRING('Hello World', 7, 5); -- World,从第7个字符开始取5个这里有个折磨过不少人的细节:LEN不计尾随空格,DATALENGTH才计实际字节数。
SELECT LEN('abc '); -- 3 SELECT DATALENGTH('abc '); -- 5更要注意的是,列类型是NVARCHAR时,DATALENGTH会按每个字符2字节计算,这跟VARCHAR(1字符1字节)不一样。如果你用DATALENGTH判断字符串长度,中文字符和NVARCHAR类型会让你莫名其妙多出一倍。所以判断字符数用LEN,判断存储空间用DATALENGTH,别混用。
截取函数有个使用细节:SUBSTRING的起始位置和长度都是基于字符位置,不是字节位置。中文字符按一个字符算,这部分没问题。但如果你要截取一段可能包含中文的字符串,请确保使用NVARCHAR而不是VARCHAR,否则中文可能变成问号或截断。
3.2 查找与替换:CHARINDEX、PATINDEX、REPLACE、STUFF
CHARINDEX用来找子串位置,PATINDEX则支持通配符模式匹配。REPLACE用来替换,STUFF则能实现"删除指定位置长度的字符串再插入新内容"。
SELECT CHARINDEX('bc', 'abcd'); -- 2,找不到返回0 SELECT PATINDEX('%[0-9]%', 'abc123'); -- 4,第一次出现数字的位置 SELECT REPLACE('abcabc', 'bc', 'XY'); -- aXYaXY SELECT STUFF('abcdef', 2, 3, 'XY'); -- aXYefCHARINDEX和PATINDEX这对组合我很常用。比如处理地址栏信息时,要看某列里是否包含手机号或者特定关键词,就用它们做判断条件。
STUFF是个容易被忽略但非常实用的函数。它比SUBSTRING加字符串拼接灵活得多。一个典型场景是脱敏:把身份证号中间几位替换成星号。
SELECT STUFF('110101199001011234', 7, 8, '********'); -- 结果:110101********1234REPLACE在清理脏数据时威力巨大。比如用户录入电话号码时混入了空格、横线,可以一次性用嵌套REPLACE去掉:
SELECT REPLACE(REPLACE(REPLACE('010-1234 5678', '-', ''), ' ', ''), ' ', '');不过要注意REPLACE是全局替换,不是只替换第一次出现的子串。如果你的需求是"只换第一个",REPLACE做不到,得配合STUFF和CHARINDEX来定位。
3.3 拼接与清洗:CONCAT、加号、LTRIM/RTRIM/UPPER/LOWER
字符串拼接是另一个高频场景。SQL Server里有两种拼接方式:加号运算符和CONCAT函数。这两者的区别非常大,尤其是遇到NULL的时候。
SELECT 'a' + 'b'; -- ab SELECT 'a' + NULL; -- NULL,灾难 SELECT CONCAT('a', NULL, 'b'); -- ab,CONCAT会跳过NULL这个坑我踩过太多次。做报表拼接地址、姓名时,如果某个字段为NULL,用加号拼接出来的结果整个变成NULL,排查半天才发现是某个地址段为空。所以只要存在空值风险,优先用CONCAT。如果用的是旧版本SQL Server(2012之前),没有CONCAT函数,那就得用ISNULL或COALESCE先兜底再拼接。
清洗字符串同样常用。LTRIM去掉左侧空格,RTRIM去掉右侧空格,SQL Server 2017以后可以直接用TRIM同时去掉两侧空格。注意它们默认只处理普通空格,换行符、制表符不在处理范围。如果想同时清理缩进,可以配合REPLACE把CHAR(9)、CHAR(10)替换掉。
大小写转换UPPER和LOWER,在处理用户输入标准化时有用,比如用户名不区分大小写、身份证号里的字母统一转大写。不过提到大小写,SQL Server本身的排序规则决定了字符串比较时候是否区分大小写。如果数据库排序规则不区分大小写,那么WHERE Name = 'abc'也能匹配'ABC'。需要严格匹配大小写时,可以用COLLATE指定排序规则。
3.4 SQL Server 2016+ 拆分与聚合:STRING_SPLIT 与 STRING_AGG
SQL Server 2016开始有STRING_SPLIT,2017开始有STRING_AGG,这俩是处理字符串与行集互转的神器。
SELECT value FROM STRING_SPLIT('apple,banana,orange', ',');结果返回三行。这个函数用来处理"逗号分隔的ID列表"非常方便,可以直接JOIN到业务表上。但有两个点要注意:一是STRING_SPLIT不保证输出顺序,也就是说你无法依赖它按原字符串顺序返回行;二是它直接返回的结果集列名是value,如果你想换成其他列名,需要加别名。
STRING_AGG则是把多行字符串聚合成一行,类似MySQL的GROUP_CONCAT。
SELECT dept, STRING_AGG(emp_name, ',') AS employees FROM employee GROUP BY dept;如果希望拼接结果有序,可以用WITHIN GROUP:
SELECT dept, STRING_AGG(emp_name, ',') WITHIN GROUP (ORDER BY emp_name) FROM employee GROUP BY dept;这个语法写起来有点长,但效果很好。STRING_AGG同样会自动忽略NULL值,不会把NULL拼进结果里。如果你要拼接的是去重后的值,目前不能直接在STRING_AGG里写DISTINCT,需要先子查询去重再聚合。
4. 数学函数与聚合函数:从单行计算到分组统计
4.1 必须掌握的数学函数
纯数学函数平时用得不那么频繁,但只要是做金额、价格、数量相关的计算,总会碰到几个。
SELECT ABS(-100); -- 100 SELECT CEILING(3.2); -- 4,向上取整 SELECT FLOOR(3.8); -- 3,向下取整 SELECT ROUND(123.456, 2); -- 123.46,四舍五入保留两位 SELECT ROUND(123.456, 2, 1); -- 123.45,第三个参数非0表示直接截断 SELECT POWER(2, 10); -- 1024 SELECT SQRT(16); -- 4 SELECT FLOOR(RAND() * 10) + 1; -- 1到10的随机整数ROUND的第三个参数是我见过很多人不知道的小细节。默认不传第三参就是四舍五入,传了非0值就变成"截断",也就是不四舍五入,后面的数字直接扔掉。这在金融计算里偶尔会遇到,比如有些客户要求"分以下直接舍掉",这时ROUND(value, 2, 1)比转字符串再截取靠谱得多。
RAND返回0到1之间的随机小数,不能直接当随机整数用,要用它配合FLOOR或CAST转换。ORDER BY NEWID()也可以实现随机排序,从结果集里随机抽N条记录:
SELECT TOP 5 * FROM Orders ORDER BY NEWID();这个方法在抽样、随机派单、随机出题场景里都很好用,但注意大表上做随机排序会有性能开销。
还有一个数学计算里的经典坑:整数除法。SQL Server里两个整数相除,结果还是整数,小数部分直接截掉。
SELECT 5 / 2; -- 2,不是2.5 SELECT 5 / 2.0; -- 2.5 SELECT CAST(5 AS DECIMAL(10,2)) / 2; -- 2.50我见过太多报表里因为忘了加.0,导致平均单价、占比算出来全是整数。比如SUM(amount) / COUNT(*) 这种组合,如果两个操作数都是INT类型,结果就是整数。写平均数、百分比的时候,一定要先把分子或分母转成小数类型。
4.2 聚合函数的底层逻辑:COUNT、SUM、AVG、MAX、MIN
聚合函数是分组统计的核心。大多数人会用,但对NULL的处理规则未必清楚。下面这个规律是理解聚合函数的关键:除了COUNT(*)之外,SUM、AVG、COUNT(列)、MAX、MIN都会忽略NULL值。
SELECT SUM(Amount) FROM Orders; SELECT AVG(Amount) FROM Orders; SELECT COUNT(*) FROM Orders; -- 所有行数 SELECT COUNT(Amount) FROM Orders; -- Amount不为NULL的行数 SELECT COUNT(DISTINCT CustomerID) FROM Orders; -- 去重客户数 SELECT MAX(OrderDate), MIN(OrderDate) FROM Orders;COUNT()和COUNT(列)的区别经常被忽略。假设某张表的折扣率列有很多NULL,COUNT()返回总行数,COUNT(DiscountRate)只返回非NULL的折扣率记录数。写报表标题"有效折扣记录数"时,这个区别直接决定了数字对不对。
MAX和MIN不只是数字使用,字符串也可以比大小,按排序规则来取最大最小;日期当然也可以,而且取最大日期就是最新日期。这在业务里很常见,比如"每个客户最近一次下单时间":
SELECT CustomerID, MAX(OrderDate) AS LastOrderDate FROM Orders GROUP BY CustomerID;还有一个容易踩的NULL陷阱:SUM和AVG在没有可汇总的行时返回NULL,不是0。比如某个部门没有任何符合条件的订单,SUM(Amount)就是NULL。如果报表里直接展示,会出现一个空白的数字,甚至导致后续计算全变NULL。处理方法是提前用ISNULL或COALESCE兜底:
SELECT dept, ISNULL(SUM(Amount), 0) AS TotalAmount FROM Orders GROUP BY dept;4.3 GROUP BY 与 HAVING:先过滤还是后过滤
聚合函数配合GROUP BY分组,基本是报表开发的家常便饭。但很多人写分组查询时没有意识到WHERE和HAVING的执行顺序。
SELECT dept, COUNT(*) AS OrderCount, SUM(Amount) AS TotalAmount FROM Orders WHERE Status = 'success' GROUP BY dept HAVING COUNT(*) > 10;执行顺序是这样的:先通过WHERE过滤行,再进行分组聚合,最后用HAVING过滤分组结果。所以WHERE后面不能写聚合函数,比如WHERE COUNT(*) > 10会直接报错;而HAVING后面也不能写原始列条件,比如HAVING Status = 'success'虽然不报错但逻辑不对。
性能上,如果能在WHERE里把数据过滤掉,尽量用WHERE,因为分组的数据越少,整个查询越快。HAVING只在"过滤聚合结果"这种WHERE做不到的场景才用。这种写法上的区分,不光是正确性问题,也会影响大表查询的速度。
另外还要提一个常见错误:GROUP BY的select列表中,只能出现分组列或者聚合函数。有的开发习惯不好,在MySQL里能跑通的写法,到了SQL Server直接报错:
-- 错误示例:Name没有出现在GROUP BY中 SELECT dept, Name, COUNT(*) FROM employee GROUP BY dept;这不是SQL Server限制得太死,而是这种查询的结果本身就不明确。正确做法是明确分组维度,或者用MAX、MIN等聚合函数把剩余列都包进去。
4.4 进阶聚合:ROLLUP、CUBE、GROUPING SETS 和窗口函数
如果只是按一个维度分组,GROUP BY就够用了。但报表经常需要"部门小计、总计"这种多级汇总,这时候ROLLUP和GROUPING SETS就派上用场了。
SELECT dept, duty, SUM(salary) AS TotalSalary FROM employee GROUP BY ROLLUP(dept, duty);这个查询会返回正常分组行、部门小计行和总计行。小计和总计行里,没有参与汇总的列会显示NULL。为了区分"正常的NULL值"和"汇总产生的NULL",可以用GROUPING函数:
SELECT CASE WHEN GROUPING(dept) = 1 THEN 'ALL' ELSE dept END AS dept, CASE WHEN GROUPING(duty) = 1 THEN 'ALL' ELSE duty END AS duty, SUM(salary) AS TotalSalary FROM employee GROUP BY GROUPING SETS ((dept, duty), (dept), ());GROUPING SETS比ROLLUP更灵活,可以自定义需要哪几层小计。这个写法在生成动态汇总报表时非常实用,虽然初看起来有点复杂,但一旦掌握,能省掉来回拼接多条SQL的麻烦。
还有一类和聚合强相关的功能是窗口函数,简单说就是"分组聚合但不合并行"。比如想要每个员工及其所在部门的工资总额:
SELECT emp_name, dept, salary, SUM(salary) OVER(PARTITION BY dept) AS dept_total FROM employee;每个员工行都会保留,同时后面多一列部门合计。这在计算占比、排名、同比环比时非常好用。窗口函数不算是严格的聚合函数,但它和聚合函数配合能解决很多复杂问题。遇到"既要明细又要汇总"的需求时,优先考虑OVER。
5. 常见问题与排查技巧实录
5.1 日期转换最容易踩的4个坑
第一个坑:样式号记混。101是MM/dd/yyyy,103是dd/MM/yyyy,120是yyyy-MM-dd HH:mm:ss,112是yyyyMMdd。搞混之后日期不会报错,但数据含义完全变了,比如把3月5日变成5月3日。建议在代码里统一约定格式,不然后期排查非常痛苦。
第二个坑:依赖字符串隐式转换。把'2024/05/01'、'2024.05.01'、'20240501'这些格式直接CAST成日期,结果跟当前数据库的语言设置强相关。最可靠的做法是显式加上样式号,比如CONVERT(DATE, '20240501', 112),这样无论谁在什么环境里跑,结果都一致。
第三个坑:DATEDIFF边界问题。前面提到过,DATEDIFF算的是边界跨越次数。统计年龄、工龄、周岁时,如果你要的是"满多少年",直接DATEDIFF(year, birth, GETDATE())会把人算大一岁,因为只要跨了元旦就加1。这种需求通常要用DATEADD构造出精确的生日判断再计算。
第四个坑:FORMAT性能。FORMAT虽然方便,但它是逐行调用CLR。大数据量分组格式化时,可能比CONVERT慢几十倍。能用样式号解决的,就别用FORMAT去格式化每一行。
5.2 函数套在列上导致索引失效
这也是一个容易忽略的问题。假设表里OrderDate列有索引,下面的查询会让SQL Server放弃索引:
SELECT * FROM Orders WHERE YEAR(OrderDate) = 2024;因为每一行都要先算出YEAR(OrderDate),才能和2024比较,索引排序根本帮不上忙。改写为范围查询就能利用索引:
SELECT * FROM Orders WHERE OrderDate >= '20240101' AND OrderDate < '20250101';这不仅仅影响执行速度,在数据量大的表上,差的可能是几十倍。养成习惯:查询条件里尽量不套函数,改成对列本身的区间判断。
5.3 聚合结果为空和NULL的处理
前面说过,SUM在没有匹配行时返回NULL。实际业务中,报表模板往往期望显示0,而不是空白。推荐统一用ISNULL包裹聚合结果:
SELECT dept, ISNULL(SUM(Amount), 0) AS TotalAmount, ISNULL(AVG(Amount), 0) AS AvgAmount FROM Orders GROUP BY dept;同样,如果想让AVG把NULL值当作0参与平均,直接AVG(ISNULL(Amount, 0))。要清楚这跟AVG默认行为(忽略NULL)的差异,别搞混。
5.4 连接不上SQL Server(含第三方软件连接案例)
搜热词时看到不少人遇到"某个软件无法连接到SQL Server",比如SolidWorks Electrical配置连接时提示连不上。这种问题通常不是函数问题,而是连接层面的问题。排查顺序一般是:
- 确认SQL Server服务是否启动。打开SQL Server配置管理器,看SQL Server服务状态是否为"正在运行"。
- 确认实例名和端口。默认实例通常是"计算机名",命名实例是"计算机名\实例名"。端口默认1433,但命名实例可能是动态端口。
- 确认TCP/IP协议是否启用。配置管理器里,SQL Server网络配置下,把TCP/IP设为已启用,并重启服务。
- 确认防火墙是否放行1433端口。
- 确认身份验证模式。如果是SQL Server身份验证,安装时选了Windows身份验证模式的话,需要改成混合模式,并设置好sa密码。
如果SSMS本身能连上、第三方软件连不上,那多半是第三方配置里的服务器名、端口或权限不对。用别名或者本机IP代替计算机名,也是常见解决办法。
5.5 SQL Server进程内存占用过高
很多人看到服务器上sqlservr.exe占了大量内存就慌,其实SQL Server的默认行为就是尽量吃掉可用内存做缓存,这是设计如此。如果你要限制它不要影响操作系统和其他应用,可以设置最大服务器内存:
EXEC sp_configure 'max server memory (MB)', 8192; RECONFIGURE;也可以在SSMS右键服务器,选择"属性-内存",设置最大服务器内存。设置完后SQL Server会自动调整,不需要重启。这个操作在SQL Server和Windows Server共存的机器上非常有必要,否则很容易出现系统内存告急。
5.6 转换失败和参数化查询
把字符串转数字失败是新手常见报错:将varchar转换为数据类型int时失败。原因往往是数据里混入了空格、中文或字母。除了用TRY_CONVERT兜底,更重要的习惯是:任何用户输入都不应该用拼字符串的方式写进SQL。
比如:
-- 非常糟糕的写法 SELECT * FROM Users WHERE UserName = '张三' + @input;这种做法会因为引号转义问题导致错误,还可能带来注入风险。正确做法是参数化查询,无论是存储过程还是应用程序里,都用参数传递输入值。SQL Server执行参数化查询时,不会把输入当作SQL代码解析,转换也交给引擎统一处理,这才是最根本的解决方案。
最后再聊几句我的个人使用习惯
函数这东西,光看不用很快就会忘。我自己的习惯是每接触一个新的SQL Server环境,第一件事是确认版本,然后建一张简单的测试表,把日期转换、字符串拆分、聚合分组这些常用操作都跑一遍,确认行为和自己预期一致。尤其在上了新版本SQL Server后,我会专门试一下STRING_SPLIT、STRING_AGG、TRIM这些新函数,看看能不能替代以前手工拼接的写法。
给刚开始学SQL Server的朋友一个建议:不要试图一次性把所有函数记住,你现在只需要记住SQL Server有这些能力,遇到问题时知道往哪个方向查。真正遇到具体需求时,你自然会去搜语法、看示例,用过几次就记住了。但有几个坑一定要提前知道——LEN不计尾随空格、COUNT(列)不等于COUNT(*)、ROUND第三参数是截断开关、SUM对空集返回NULL而不是0。这些坑在真实项目里一旦踩中,排查起来极其浪费时间。
如果你平时工作里经常写报表,或者要处理各种历史数据,建议把这些函数例子都自己敲一遍,花不了多长时间,但之后写SQL会顺很多。等用得多了,你也会慢慢形成自己的一套速查清单,到那时候,这些函数就不再是知识点,而是你解决问题的本能了。