Почему вычисляемый столбец 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

У меня есть модель dbml в C # (LinqToSQL) с представлением FilteredRealty в нем. Поле [Ranking] распознается как обнуляемое целое, и поэтому мне приходится фиксировать тип в сгенерированном коде каждый раз, когда я что-то изменяю в базе данных. Это очень расстраивает меня и много ручной работы.

В FilteredRealty нет агрегатов (относительно этого связанного вопроса ).

Почему столбец Рейтинг представления считается обнуляемым, если Realty.Ranking не обнуляется ?

Томас Кубес
источник

Ответы:

19

[Ranking]Поле показывает , как «Nullable» из - за того , чтобы быть вычисляемый столбец. Да, он объявлен как NOT NULL, но, как указано на странице MSDN для вычисляемых столбцов , ядро ​​базы данных может изменить это определение во время запроса:

Компонент Database Engine автоматически определяет обнуляемость вычисляемых столбцов на основе используемых выражений. Результат большинства выражений считается обнуляемым, даже если присутствуют только ненулевые столбцы, поскольку возможные недопустимые значения или переполнения также приведут к нулевым результатам. Используйте функцию COLUMNPROPERTY со свойством AllowsNull, чтобы исследовать обнуляемость любого вычисляемого столбца в таблице. Выражение со значением NULL можно превратить в ненулевое значение, указав 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  ==  :-(

Теперь давайте посмотрим, работает ли их совет относительно 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

Пока это выглядит позитивно, но даже первоначальное определение сообщило «НЕ НУЛЬ» из этих двух проверок. Итак, давайте попробуем настоящий тест - как ядро ​​базы данных определяет обнуляемость во время выполнения:

SELECT * FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM dbo.Realty', '', NULL);
-- RankingFixed: is_nullable = 0  ==  :-) WOO HOO!
Соломон Руцкий
источник
13

Чтобы гарантировать, что выражение вычисляемого столбца ранжирования не возвращает NULL ни при каких обстоятельствах, вы должны обернуть его ISNULLподходящим значением по умолчанию. Например:

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

NOT NULLОграничение гарантирует , сохраненное значение не равно нулю, в контексте table- и сеансового уровня настройки в действие , когда таблица изменяется.

Однако, когда запрос ссылается на это выражение, 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потому что ваше представление не привязано к схеме.

Пол Уайт 9
источник