关键差异:
isnull
使用第一个参数的数据类型;coalesce
按case
规则返回「最高优先级」的数据类型。
下面用一组可直接运行的 T‑SQL 示例,展示 isnull
因「结果类型取决于第一个参数的数据类型」,从而导致的隐式截断、类型不符的问题,并给出等价的 coalesce
或显式 cast
规避方式。
1. 值被截断
SQL
1-- 演示:C1 比较短(varchar(6)),C2 较长(varchar(15))
2create table #WXJ (C1 varchar(6), C2 varchar(15));
3insert into #WXJ values (null, '12三四五六');
4
5-- ISNULL:结果类型取第一个参数(C1)的类型与长度 => 截断为 6 个字节
6select C1, C2, isnull(C1, C2) as bad_data -- 仅返回 '12三四'
7from #WXJ;
8
9-- 两种修复:
10-- A) 显式扩宽第一个参数,让结果类型足够长
11select C1, C2, isnull(convert(varchar(15), C1), C2) as good_data
12from #WXJ;
13
14-- B) 用 coalesce(按 case 规则推断,避免被第一个参数长度限制)
15select C1, C2, coalesce(C1, C2) as good_data
16from #WXJ;
17
18drop table #WXJ;
2. 类型不兼容报错
SQL
1-- 演示:当第一个参数为 int,第二个参数为 datetime
2declare @i int = null, @dt datetime = null;
3
4-- ❌ 报错 [不允许从数据类型 datetime 到 int 的隐式转换。请使用 CONVERT 函数来运行此查询。]
5-- select isnull(@i, @dt);
6
7-- 但若第一个参数是 datetime,则会把 0 转为 datetime('1900-01-01 00:00:00.000')
8select isnull(@dt, 0); -- 结果为:1900-01-01 00:00:00.000
9
10-- 这里用 coalesce 也是一样的
11-- 因为 coalesce 按 case 规则返回「最高优先级」的数据类型,而 datetime 优先级高于 int
12select coalesce(@dt, 0); -- 结果为:1900-01-01 00:00:00.000
3. 最佳实践
coalesce
可以处理多个参数,返回第一个非空值,且结果类型遵循case
规则,可避免isnull
的隐式截断和类型不兼容问题- 需要非空归一化时,可使用
isnull
,因为能更清晰地表达意图(可有效避免 IDE 的静态分析误报) - 正因为
isnull
语意明确,所以在需要将计算列用于主键时,直接使用isnull
即可
计算列主键使用 ISNULL
有一个例外是在需要将计算列用于主键时,推荐使用 isnull
:
isnull
定义的计算列更容易被系统判为not null
,因此直接能当primary key
/unique
- 而
coalesce
的可空性推断导致直接建主键失败
SQL
1-- 删除旧表(如果已存在)
2if object_id('dbo.WXJ_TEST') is not null
3 drop table dbo.WXJ_TEST;
4
5-- 创建表
6create table dbo.WXJ_TEST (
7 col1 varchar(50),
8 clo2 as isnull(col1, 0) primary key -- 👍 计算列使用 isnull 更省事
9
10 -- ❌ 报错 [不能对表 'WXJ_TEST' 中的列 'clo2' 定义 PRIMARY KEY 约束。必须将计算列持久化,并且该列不可为 Null。]
11 -- clo2 as coalesce(col1, 0) primary key
12);
13
14-- 插入第一行:col1 为 null -> col2 计算为 0
15insert into dbo.WXJ_TEST values (null);
16
17-- ❌ 报错 [违反了 PRIMARY KEY 约束“PK__WXJ_TEST__22FEC0BB5BA98254”。不能在对象“dbo.WXJ_TEST”中插入重复键。重复键值为 (0)。]
18insert into dbo.WXJ_TEST(col1) values (null);
19
20-- 插入非空且不重复的值均可
21INSERT INTO dbo.WXJ_TEST(col1) VALUES (1), (2);