¿Por qué una columna calculada NOT NULL se considera nula en una vista?

15

Tengo una mesa:

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

Y una vista:

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

Tengo un modelo dbml en C # (LinqToSQL) con la vista FilteredRealty . El campo [Clasificación] se reconoce como un int anulable y, por lo tanto, tengo que corregir el tipo en el código generado cada vez que cambio algo en la base de datos. Esto es muy frustrante para mí y mucho trabajo manual.

No hay agregados utilizados en FilteredRealty (con respecto a esta pregunta relacionada ).

¿Por qué la columna Clasificación de la vista se considera como nulable si Realty.Ranking no es nulable ?

Tomás Kubes
fuente

Respuestas:

19

El [Ranking]campo se muestra como "Anulable" debido a que es una columna calculada. Sí, se declara como NOT NULL, pero como dice la página de MSDN para Columnas calculadas, el motor de la base de datos puede cambiar esa determinación en el momento de la consulta:

El Motor de base de datos determina automáticamente la nulabilidad de las columnas calculadas en función de las expresiones utilizadas. El resultado de la mayoría de las expresiones se considera anulable incluso si solo hay columnas no anulables, porque los posibles desbordamientos o desbordamientos producirán resultados nulos también. Use la función COLUMNPROPERTY con la propiedad AllowsNull para investigar la nulabilidad de cualquier columna calculada en una tabla. Una expresión que puede anularse puede convertirse en una no anulable especificando ISNULL ( check_expression , constant ), donde la constante es un valor no nulo sustituido por cualquier resultado nulo.

Entonces, veamos si esto es cierto:

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

Ahora veamos si su consejo con respecto a ISNULLfunciona:

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

Sus consejos parecen precisos, así que intentemos aplicar eso a la definición de la columna calculada:

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

Y ahora verificamos las propiedades nuevamente, pero para el nuevo campo:

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

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

Esto parece positivo hasta ahora, pero incluso la definición original informó "NO NULO" de estas dos comprobaciones. Entonces, intentemos la prueba real: cómo el motor de la base de datos determina la nulabilidad en tiempo de ejecución:

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

Para garantizar que la expresión de columna calculada Clasificación no devuelva NULL en ninguna circunstancia, debe ajustarla ISNULLcon un valor predeterminado adecuado. Por ejemplo:

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

La NOT NULLrestricción garantiza que el valor persistente no sea nulo, en el contexto de la configuración de nivel de tabla y sesión vigente cuando se modifica la tabla.

Sin embargo, cuando una consulta hace referencia a esa expresión, SQL Server puede elegir entre usar el valor persistente (si la configuración coincide) o calcular la expresión de nuevo.

Algunas configuraciones de sesión pueden hacer que un desbordamiento devuelva NULL, por ejemplo, por lo que SQL Server debe tener en cuenta esta posibilidad. Cuando se accede a través de la vista, SQL Server marca correctamente la columna como potencialmente devolviendo un NULL.

El uso de un elemento externo ISNULLen la expresión es la única forma compatible de lograr lo que desea. Usar COALESCEno funcionará, por ejemplo.

Manifestación:

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

Tenga en cuenta el uso de sys.sp_refreshsqlmoduleporque su vista no está vinculada al esquema.

Paul White 9
fuente