为什么 NOT NULL 计算列在视图中被认为可以为空?

Tom*_*bes 17 null sql-server view computed-column

我有一张桌子:

CREATE TABLE [dbo].[Realty](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [RankingBonus] [int] NOT NULL,
    [Ranking]  AS ([Id]+[RankingBonus]) PERSISTED NOT NULL
    ....
)
Run Code Online (Sandbox Code Playgroud)

还有一个观点:

CREATE View  [dbo].[FilteredRealty] AS
 SELECT 
realty.Id as realtyId,
...
COALESCE(realty.Wgs84X, ruian_cobce.Wgs84X, ruian_obec.Wgs84X) as Wgs84X,
COALESCE(realty.Wgs84Y, ruian_cobce.Wgs84Y, ruian_obec.Wgs84Y) as Wgs84Y,
realty.Ranking,
...
FROM realty
JOIN Category ON realty.CategoryId = Category.Id
LEFT JOIN ruian_cobce ON realty.cobceId = ruian_cobce.cobce_kod
LEFT JOIN ruian_obec ON realty.obecId = ruian_obec.obec_kod
LEFT JOIN okres ON realty.okresId = okres.okres_kod
LEFT JOIN ExternFile ON realty.Id = ExternFile.ForeignId AND ExternFile.IsMain = 1
                     AND ExternFile.ForeignTable = 5
INNER JOIN Person ON realty.OwnerId = Person.Id
WHERE Person.ConfirmStatus = 1
Run Code Online (Sandbox Code Playgroud)

我在 C# (LinqToSQL) 中有一个 dbml 模型,其中包含FilteredRealty视图。的[排名]字段被识别为可空int和所以我必须每次固定的类型在所生成的代码,当我更改数据库中的任何东西。这对我和大量手工工作来说非常令人沮丧。

FilteredRealty中没有使用聚合(关于这个相关问题)。

如果Realty.Ranking不可为空,为什么视图的Ranking列被视为可以为空?

Sol*_*zky 23

[Ranking]由于是计算列,该字段显示为“可空”。是的,它被声明为NOT NULL,但正如计算列的 MSDN 页面所述,数据库引擎可以在查询时更改该确定:

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

那么,让我们看看这是否属实:

CREATE TABLE [dbo].[Realty](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [RankingBonus] [int] NOT NULL,
    [Ranking]  AS ([Id]+[RankingBonus]) PERSISTED NOT NULL
);
GO

EXEC sp_help 'dbo.Realty';
-- Ranking: Nullable = "no"

SELECT COLUMNPROPERTY(OBJECT_ID(N'dbo.Realty'), N'Ranking', 'AllowsNull') AS [AllowsNull?];
-- 0

SELECT * FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM dbo.Realty', '', NULL);
-- Ranking: is_nullable = 1  ==  :-(
Run Code Online (Sandbox Code Playgroud)

现在让我们看看他们的建议是否ISNULL有效:

SELECT * FROM sys.dm_exec_describe_first_result_set(
   N'SELECT Id, RankingBonus, ISNULL(Ranking, -99) AS [RealRanking] FROM dbo.Realty;',
   '',
   NULL);
-- RealRanking: is_nullable = 0
Run Code Online (Sandbox Code Playgroud)

他们的建议看起来确实很准确,所以让我们尝试将其应用于计算列的定义:

ALTER TABLE dbo.Realty
  ADD [RankingFixed] AS (ISNULL(([Id]+[RankingBonus]), -99))
  PERSISTED NOT NULL;
GO
Run Code Online (Sandbox Code Playgroud)

现在我们再次检查属性,但对于新字段:

EXEC sp_help 'dbo.Realty';
-- RankingFixed: Nullable = "no"

SELECT COLUMNPROPERTY(OBJECT_ID(N'dbo.Realty'),
                      N'RankingFixed',
                      'AllowsNull') AS [AllowsNullsNow?];
-- 0
Run Code Online (Sandbox Code Playgroud)

到目前为止,这看起来是积极的,但即使是原始定义也从这两个检查中报告了“NOT NULL”。那么让我们尝试真正的测试——数据库引擎如何在运行时确定可空性:

SELECT * FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM dbo.Realty', '', NULL);
-- RankingFixed: is_nullable = 0  ==  :-) WOO HOO!
Run Code Online (Sandbox Code Playgroud)


Pau*_*ite 13

为保证Ranking计算列表达式在任何情况下都不会返回 NULL,您必须ISNULL使用合适的默认值将其包装起来。例如:

Ranking AS ISNULL(Id + RankingBonus, 0) PERSISTED NOT NULL
Run Code Online (Sandbox Code Playgroud)

NOT NULL在修改表时生效的表级和会话级设置的上下文中,该约束确保持久值不为空。

但是,当查询引用该表达式时,SQL Server 可以选择使用持久值(如果设置匹配)或重新计算表达式。

例如,某些会话设置会导致溢出返回 NULL,因此 SQL Server 必须考虑这种可能性。通过视图访问时,SQL Server 正确地将该列标记为可能返回 NULL。

ISNULL在表达式上使用最外层是实现您想要的唯一支持的方式。COALESCE例如,使用将不起作用。

演示:

CREATE TABLE dbo.T1
(
    c1 integer NOT NULL,
    c2 integer NOT NULL,
    c3 AS c1 + c2 PERSISTED NOT NULL
);
GO
CREATE VIEW dbo.V1
AS
SELECT T.c1,
       T.c2,
       T.c3
FROM dbo.T1 AS T;
GO
SELECT AllowsNull = COLUMNPROPERTY(OBJECT_ID(N'dbo.V1', N'V'), N'c3', 'AllowsNull');
GO
ALTER TABLE dbo.T1
DROP COLUMN c3;
GO
ALTER TABLE dbo.T1
ADD c3 AS ISNULL(c1 + c2, 0) PERSISTED NOT NULL;
GO
EXECUTE sys.sp_refreshsqlmodule
    @name = N'dbo.V1';
GO
SELECT AllowsNull = COLUMNPROPERTY(OBJECT_ID(N'dbo.V1', N'V'), N'c3', 'AllowsNull');
GO
DROP VIEW dbo.V1;
DROP TABLE dbo.T1;
GO
Run Code Online (Sandbox Code Playgroud)

请注意使用,sys.sp_refreshsqlmodule因为您的视图不是模式绑定的。