【发布时间】:2017-03-23 15:03:27
【问题描述】:
假设我们有如下表格:
CREATE TABLE dbo.tab(id INT PRIMARY KEY
-- other columns
,is_active BIT);
INSERT INTO dbo.tab(id, is_active)
VALUES (1, NULL), (2, 1), (3,0);
案例是添加计算列,将NULL更改为0,并尽可能返回原始值。
最终结果:
- 原始列必须保持不变(因此不可能
UPDATE和ALTER表) - 计算的列必须具有正确的类型和可空性
- 无法使用
VIEW/TRIGGER/...
所以让我们添加该列:
CREATE TABLE dbo.tab(
id INT PRIMARY KEY
,is_active BIT
,calc_flag1 AS CAST(IIF(is_active IS NULL,0 ,is_active) AS BIT)
,calc_flag2 AS CAST(IIF(is_active IS NULL,0 ,ISNULL(is_active,0)) AS BIT)
,calc_flag3 AS IIF(is_active IS NULL,0 , ISNULL(is_active,0))
,calc_flag4 AS CAST(ISNULL(IIF(is_active IS NULL,0 , is_active), 0) AS BIT)
,calc_flag5 AS ISNULL(IIF(is_active IS NULL,0 ,is_active),0)
,calc_flag6 AS ISNULL(CAST(IIF(is_active IS NULL,0 ,is_active) AS BIT),
CAST(0 AS BIT))
);
数据:
SELECT * FROM dbo.tab;
╔══╦═════════╦══════════╦══════════╦══════════╦══════════╦══════════╦══════════╗
║id║is_active║calc_flag1║calc_flag2║calc_flag3║calc_flag4║calc_flag5║calc_flag6║
╠══╬═════════╬══════════╬══════════╬══════════╬══════════╬══════════╬══════════╣
║ 1║ NULL ║ False ║ False ║ 0 ║ False ║ 0 ║ False ║
║ 2║ True ║ True ║ True ║ 1 ║ True ║ 1 ║ True ║
║ 3║ False ║ False ║ False ║ 0 ║ False ║ 0 ║ False ║
╚══╩═════════╩══════════╩══════════╩══════════╩══════════╩══════════╩══════════╝
以及元数据检查:
EXEC sp_help 'dbo.tab';
╔═════════════╦══════╦══════════╦════════╦══════╦═══════╦══════════╗
║ Column_name ║ Type ║ Computed ║ Length ║ Prec ║ Scale ║ Nullable ║
╠═════════════╬══════╬══════════╬════════╬══════╬═══════╬══════════╣
║ id ║ int ║ no ║ 4 ║ 10 ║ 0 ║ no ║
║ is_active ║ bit ║ no ║ 1 ║ ║ ║ yes ║
║ calc_flag1 ║ bit ║ yes ║ 1 ║ ║ ║ yes ║
║ calc_flag2 ║ bit ║ yes ║ 1 ║ ║ ║ yes ║
║ calc_flag3 ║ int ║ yes ║ 4 ║ 10 ║ 0 ║ no ║
║ calc_flag4 ║ bit ║ yes ║ 1 ║ ║ ║ yes ║
║ calc_flag5 ║ int ║ yes ║ 4 ║ 10 ║ 0 ║ no ║
║ calc_flag6 ║ bit ║ yes ║ 1 ║ ║ ║ no ║
╚═════════════╩══════╩══════════╩════════╩══════╩═══════╩══════════╝
第一次尝试:
,calc_flag1 AS CAST(IIF(is_active IS NULL,0 ,is_active) AS BIT)
正确的数据类型,但它不能为空。我可以理解,因为它具有硬编码的值和可以为空的列,所以整个表达式被评估为可以为空。
第二次尝试:
,calc_flag2 AS CAST(IIF(is_active IS NULL,0 ,ISNULL(is_active,0)) AS BIT)
与以前相同,但带有明确的ISNULL(is_active, 0)。现在它应该可以工作了,因为有硬编码的值和ISNULL,但它没有。
,calc_flag3 AS IIF(is_active IS NULL,0 , ISNULL(is_active,0))
这很有趣,没有CAST 它得到nullable - no,但数据类型现在是INT。
第三次尝试:
,calc_flag4 AS CAST(ISNULL(IIF(is_active IS NULL,0 , is_active), 0) AS BIT)
当第二个值被硬编码时,投射ISNULL。 为什么可以是nullable?
,calc_flag5 AS ISNULL(IIF(is_active IS NULL,0 ,is_active),0)
当然不用强制转换它就可以正常工作。
最后的尝试:
,calc_flag6 AS ISNULL(CAST(IIF(is_active IS NULL,0 ,is_active) AS BIT),
CAST(0 AS BIT))
现在我得到了正确的数据类型和可空性,但它有点难看。
问题是为什么在使用calc_flag2 或calc_flag4 时它会以这种方式运行并且无法获得正确的元数据。
【问题讨论】:
-
很好奇...我已投票关闭并迁移到 dba 站点,希望 Paul White 或在 SQL Server 内部具有类似专业知识的人会看到它。值得一提的是,您可以简单地使用
calc_flag7 AS ISNULL(is_active, 0)获得一个不可为空的位列 -
我认为问题可以简化为为什么
ISNULL(is_active,0)产生一个不可为空的位列,但简单地添加一个像CONVERT(BIT, ISNULL(is_active,0))这样的转换会导致同一列可以为空。使用this answer 中的特定部分(我知道 Paul White 会有答案!)原因是某些设置会话可能导致转换溢出返回 null,因此确保不可为 null 列的唯一方法是如果最外层函数是ISNULL。 -
另一个注意事项是
CASE表达式只有在 all 返回表达式不可为空时才具有不可为空的返回类型,所以即使当您使用IIF(is_active IS NULL,0,is_active), false 的返回表达式如果为 null 则永远不会到达,它仍然是一个可以为 null 的返回类型,所以返回的类型是可以为 null 的。 -
@GarethD 是的,现在它有了完美的意义。感谢您的解释和链接。你会编译答案吗?
标签: sql sql-server tsql sql-server-2012 calculated-columns