【问题标题】:SQL Server calculated column's metadataSQL Server 计算列的元数据
【发布时间】: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,并尽可能返回原始值。

最终结果:

  • 原始列必须保持不变(因此不可能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 时它会以这种方式运行并且无法获得正确的元数据。

【问题讨论】:

  • 很好奇...我已投票关闭并迁移到 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


【解决方案1】:

根据 MSDN,...

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

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

所以我想说,由于您的 is_active 字段可以为空,引擎会计算出仍有可能达到空条件​​,直到您在最终的 ISNULL 中特别防范它们。

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

【讨论】:

    【解决方案2】:

    首先要注意的是,当使用IIF(在后台扩展为CASE表达式)时,只有在all返回表达式不是可以为空,所以当你使用时:

    IIF(is_active IS NULL,0,is_active)
    

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

    我认为问题可以简化为为什么 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 中的特定部分(归功于Paul White),原因是某些设置会话可能导致转换溢出返回null,因此确保不可为空的列的唯一方法是如果最外面的函数是ISNULL

    如上所示,只需使用ISNULL(is_active, 0) 即可实现所需的解决方案,因为这将返回一个不可为空的位列,但值得注意的是,如果需要转换,例如,如果您需要它是一个 int列,则转换必须在 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';
    

    这给出了相关的结果

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 2022-08-23
      • 2016-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多