SQL Server计算列的元数据

时间:2016-11-09 17:52:50

标签: sql sql-server tsql sql-server-2012 calculated-columns

假设我们有如下表格:

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并尽可能返回原始值。

最终结果:

  • 原始列必须保持不变(因此无法UPDATEALTER表格)
  • 计算列必须具有正确的类型和可为空性
  • 无法使用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))
);

LiveDemo

数据:

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_flag2calc_flag4时,它的行为方式无法获取正确的元数据。

2 个答案:

答案 0 :(得分:2)

根据MSDN,...

  

除非另有说明,否则计算列是未物理存储在表中的虚拟列。每次在查询中引用它们时,都会重新计算它们的值。

     

数据库引擎根据使用的表达式自动确定计算列的可为空性。即使只存在不可存在的列,大多数表达式的结果也被认为是可空的,因为可能的下溢或溢出也会产生空结果。使用带有AllowNull属性的COLUMNPROPERTY函数可以调查表中任何计算列的可为空性。 可以通过指定ISNULL(check_expression,constant)将可以为空的表达式转换为不可为空的表达式,其中常量是替换任何空结果的非空值。

所以我要说,因为你的is_active字段是可空的,所以引擎会计算出在最后的ISNULL中特别防范它们之前仍然可能达到空状态。

我将尝试创建一个溢出或下溢,在插入时导致位列为null,以便验证引擎,但您的问题似乎有效,因为您在计算列中的表达式特别保护使用案例陈述。

答案 1 :(得分:1)

首先要注意的是,当使用IIF(在幕后扩展为CASE表达式)时,如果全部,结果将只能为空返回表达式不可为空,因此当您使用:

IIF(is_active IS NULL,0,is_active)

虽然逻辑上当你在表达式中找到is_active为false时,由于条件设置,它永远不会为null,这与编译器无关,它只能看到返回的表达式之一是{{ 1}}这是一个可以为空的列,因此返回的类型是可空的。

我认为问题可以简化到为什么ISNULL(is_active,0)产生一个不可为空的位列,但只是添加一个转换,如CONVERT(BIT,ISNULL(is_active,0)),导致同一列为空。

快速演示:

is_active

其中给出了

的相关结果
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';

使用this answer中的特定部分(Credit to Paul White),原因是某些设置会话可能导致转换溢出返回null,因此确保不可为空的列的唯一方法是最外面的函数是Column_name Type Computed Nullable -------------------------------------------- id int no no is_active bit no yes calc_flag1 bit yes no calc_falg2 bit yes yes

如上所示,可以使用ISNULL简单地实现所需的解决方案,因为这会返回一个不可为空的位列,但值得注意的是,如果需要转换,例如,如果您需要它一个int列,然后转换必须在ISNULL(is_active, 0)内。由于ISNULL将返回第一个参数的类型,因此只需要进行一次转换,例如

ISNULL

其中给出了

的相关结果
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';