SQL 服务器计算列的元数据
SQL Server calculated column's metadata
假设我们有如下 table:
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
和 return 原始值。
最终结果:
- 原始列必须保持不变(因此不可能
UPDATE
和 ALTER
table)
- 计算列必须具有正确的类型和可空性
- 不能使用
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
时它会这样并且无法获得正确的元数据。
根据 MSDN,...
Unless otherwise specified, computed columns are virtual columns that are not physically stored in the table. Their values are recalculated every time they are referenced in a query.
The Database Engine automatically determines the nullability of computed columns based on the expressions used. The result of most expressions is considered nullable even if only nonnullable columns are present, because possible underflows or overflows will produce null results as well. Use the COLUMNPROPERTY function with the AllowsNull property to investigate the nullability of any computed column in a table. An expression that is nullable can be turned into a nonnullable one by specifying ISNULL(check_expression, constant), where the constant is a nonnull value substituted for any null result.
所以我要说的是,由于您的 is_active 字段可以为空,因此引擎计算出仍然有可能达到空条件,直到您在最后的 ISNULL 中特别防范它们。
我将尝试创建一个上溢或下溢,在插入时导致位列为空,以验证引擎,但你的问题似乎确实有效,因为你在计算列中的表达式特别使用 case 语句的守卫。
首先要注意的是,当使用 IIF
(在幕后扩展为 CASE
表达式)时,只有 all return 表达式不可为空,因此当您使用:
IIF(is_active IS NULL,0,is_active)
虽然从逻辑上讲,当你在表达式中到达 is_active
时,由于条件集,它永远不会为空,这与编译器无关,它只能看到 returned 表达式是 is_active
,它是一个可以为 null 的列,因此类型 returned 是可以为 null 的。
我认为问题可以简化为为什么 ISNULL(is_active,0) 会产生一个不可空的位列,而只需添加一个像 CONVERT(BIT, ISNULL(is_active, 0)), 导致同一列可以为空。
快速演示:
CREATE TABLE #tab(
id INT PRIMARY KEY
,is_active BIT
,calc_flag1 AS ISNULL(is_active, 0)
,calc_falg2 AS CONVERT(BIT, ISNULL(is_active, 0))
);
EXECUTE tempdb.dbo.sp_help '#tab';
给出了
的相关结果
Column_name Type Computed Nullable
--------------------------------------------
id int no no
is_active bit no yes
calc_flag1 bit yes no
calc_falg2 bit yes yes
使用 this answer (Credit to Paul White 中的特定部分)原因是某些设置会话可能导致转换溢出到 return null,因此确保不可为 null 的列的唯一方法是如果外部大多数函数是 ISNULL
.
只需使用 ISNULL(is_active, 0)
即可实现所需的解决方案,如上所示,因为此 return 是一个不可为空的位列,但值得注意的是,如果需要转换,例如,如果你需要它是一个 int 列,那么转换必须在 ISNULL
内。由于 ISNULL
将 return 第一个参数的类型,因此只需要一次转换,例如
CREATE TABLE #tab(
id INT PRIMARY KEY
,is_active BIT
,calc_flag1 AS ISNULL(is_active, 0)
,calc_falg2 AS CONVERT(BIT, ISNULL(is_active, 0))
,calc_flag_int AS ISNULL(CONVERT(INT, is_active), 0)
);
EXECUTE tempdb.dbo.sp_help '#tab';
给出了
的相关结果
Column_name Type Computed Nullable
--------------------------------------------
id int no no
is_active bit no yes
calc_flag1 bit yes no
calc_falg2 bit yes yes
calc_falg_int int yes no
假设我们有如下 table:
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
和 return 原始值。
最终结果:
- 原始列必须保持不变(因此不可能
UPDATE
和ALTER
table) - 计算列必须具有正确的类型和可空性
- 不能使用
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
时它会这样并且无法获得正确的元数据。
根据 MSDN,...
Unless otherwise specified, computed columns are virtual columns that are not physically stored in the table. Their values are recalculated every time they are referenced in a query.
The Database Engine automatically determines the nullability of computed columns based on the expressions used. The result of most expressions is considered nullable even if only nonnullable columns are present, because possible underflows or overflows will produce null results as well. Use the COLUMNPROPERTY function with the AllowsNull property to investigate the nullability of any computed column in a table. An expression that is nullable can be turned into a nonnullable one by specifying ISNULL(check_expression, constant), where the constant is a nonnull value substituted for any null result.
所以我要说的是,由于您的 is_active 字段可以为空,因此引擎计算出仍然有可能达到空条件,直到您在最后的 ISNULL 中特别防范它们。
我将尝试创建一个上溢或下溢,在插入时导致位列为空,以验证引擎,但你的问题似乎确实有效,因为你在计算列中的表达式特别使用 case 语句的守卫。
首先要注意的是,当使用 IIF
(在幕后扩展为 CASE
表达式)时,只有 all return 表达式不可为空,因此当您使用:
IIF(is_active IS NULL,0,is_active)
虽然从逻辑上讲,当你在表达式中到达 is_active
时,由于条件集,它永远不会为空,这与编译器无关,它只能看到 returned 表达式是 is_active
,它是一个可以为 null 的列,因此类型 returned 是可以为 null 的。
我认为问题可以简化为为什么 ISNULL(is_active,0) 会产生一个不可空的位列,而只需添加一个像 CONVERT(BIT, ISNULL(is_active, 0)), 导致同一列可以为空。
快速演示:
CREATE TABLE #tab(
id INT PRIMARY KEY
,is_active BIT
,calc_flag1 AS ISNULL(is_active, 0)
,calc_falg2 AS CONVERT(BIT, ISNULL(is_active, 0))
);
EXECUTE tempdb.dbo.sp_help '#tab';
给出了
的相关结果Column_name Type Computed Nullable
--------------------------------------------
id int no no
is_active bit no yes
calc_flag1 bit yes no
calc_falg2 bit yes yes
使用 this answer (Credit to Paul White 中的特定部分)原因是某些设置会话可能导致转换溢出到 return null,因此确保不可为 null 的列的唯一方法是如果外部大多数函数是 ISNULL
.
只需使用 ISNULL(is_active, 0)
即可实现所需的解决方案,如上所示,因为此 return 是一个不可为空的位列,但值得注意的是,如果需要转换,例如,如果你需要它是一个 int 列,那么转换必须在 ISNULL
内。由于 ISNULL
将 return 第一个参数的类型,因此只需要一次转换,例如
CREATE TABLE #tab(
id INT PRIMARY KEY
,is_active BIT
,calc_flag1 AS ISNULL(is_active, 0)
,calc_falg2 AS CONVERT(BIT, ISNULL(is_active, 0))
,calc_flag_int AS ISNULL(CONVERT(INT, is_active), 0)
);
EXECUTE tempdb.dbo.sp_help '#tab';
给出了
的相关结果Column_name Type Computed Nullable
--------------------------------------------
id int no no
is_active bit no yes
calc_flag1 bit yes no
calc_falg2 bit yes yes
calc_falg_int int yes no