Perché una colonna calcolata NOT NULL è considerata nullable in una vista?


15

Ho un tavolo:

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

E 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

Ho un modello dbml in C # (LinqToSQL) con la vista FilteredRealty in esso. Il campo [Classifica] è riconosciuto come int nullable e quindi devo correggere il tipo nel codice generato ogni volta che cambio qualcosa nel database. Questo è molto frustrante per me e un sacco di lavoro manuale.

Non ci sono aggregati usati in FilteredRealty (riguardo a questa domanda correlata ).

Perché la colonna Classifica della vista è considerata nullable se Realty.Ranking non è nullable?

Risposte:


19

Il [Ranking]campo viene visualizzato come "Nullable" a causa della colonna calcolata. Sì, viene dichiarato come NOT NULL, ma come indicato nella pagina MSDN per le colonne calcolate , il motore di database può modificare tale determinazione al momento della query:

Motore di database determina automaticamente la nullità delle colonne calcolate in base alle espressioni utilizzate. Il risultato della maggior parte delle espressioni è considerato nullable anche se sono presenti solo colonne non annullabili, poiché anche possibili underflow o overflow producono risultati nulli. Utilizzare la funzione COLUMNPROPERTY con la proprietà AllowsNull per esaminare la nullità di qualsiasi colonna calcolata in una tabella. Un'espressione che è nullable può essere trasformata in una non annullabile specificando ISNULL ( check_expression , costante ), dove la costante è un valore non null sostituito per qualsiasi risultato nullo.

Quindi, vediamo se questo è vero:

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

Ora vediamo se i loro consigli per quanto riguarda le ISNULLopere:

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

Il loro consiglio sembra accurato, quindi proviamo ad applicarlo alla definizione della colonna calcolata:

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

E ora controlliamo di nuovo le proprietà, ma per il nuovo campo:

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

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

Questo sembra finora positivo, ma anche la definizione originale riportava "NON NULL" da questi due controlli. Quindi proviamo il vero test: come il motore di database determina la nullità in fase di esecuzione:

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

13

Per garantire che l' espressione della colonna calcolata del ranking non restituisca NULL in nessun caso, è necessario racchiuderla ISNULLcon un valore predefinito adeguato. Per esempio:

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

Il NOT NULLvincolo garantisce che il valore persistente non sia nullo, nel contesto delle impostazioni a livello di tabella e sessione in vigore quando la tabella viene modificata.

Tuttavia, quando una query fa riferimento a quell'espressione, SQL Server può scegliere se utilizzare il valore persistente (se le impostazioni corrispondono) o calcolare nuovamente l'espressione.

Alcune impostazioni di sessione possono causare un overflow che restituisce NULL, ad esempio, quindi SQL Server deve tenere conto di questa possibilità. Quando si accede tramite la vista, SQL Server contrassegna correttamente la colonna come potenzialmente restituendo un NULL.

L'uso di un valore più esterno ISNULLnell'espressione è l'unico modo supportato per ottenere ciò che desideri. L'uso COALESCEnon funzionerà, ad esempio.

demo:

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

Nota l'uso di sys.sp_refreshsqlmoduleperché la tua vista non è schematica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.