为什么在视图中将NOT NULL计算列视为可为空?


15

我有一张桌子:

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

和一个视图:

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

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

FilteredRealty中没有使用任何聚合(关于此相关问题)。

如果Realty.Ranking是不可为空的,为什么视图的“ 排名”列被视为可为空?

Answers:


19

由于该[Ranking]字段是计算列,因此显示为“ Nullable”。是的,它声明为NOT NULL,但是当“ 计算列”的MSDN页面状态时,数据库引擎可以在查询时更改该确定:

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

因此,让我们看看这是否正确:

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  ==  :-(

现在,让我们看看他们的建议是否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

他们的建议似乎确实正确,因此让我们尝试将其应用于计算列的定义:

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

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

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

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

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

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

13

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

Ranking AS ISNULL(Id + RankingBonus, 0) PERSISTED NOT NULL

NOT NULL约束确保在修改表时有效的表级和会话级设置的上下文中,持久值不为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

请注意使用,sys.sp_refreshsqlmodule因为您的视图没有架构绑定。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.