テーブルがあります:
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
FilteredRealtyビューを含むC#(LinqToSQL)のdbmlモデルがあります。 [Ranking]フィールドはnull許容のintとして認識されるため、データベースで何かを変更するたびに、生成されたコードの型を修正する必要があります。これは私と多くの手作業にとって非常にイライラします。
FilteredRealtyで使用される集計はありません( この関連する質問 に関して)。
Realty.Rankingがnull可能でない場合、ビューのRanking列がnull可能と見なされるのはなぜですか?
[Ranking]
フィールドは、計算列であるため「Nullable」と表示されています。はい、それはNOT NULL
として宣言されていますが、MSDNページ 計算列 の状態として、データベースエンジンはクエリ時にその決定を変更できます。
データベースエンジンは、使用された式に基づいて、計算列のNULL可能性を自動的に決定します。 null可能でない列のみが存在する場合でも、ほとんどの式の結果はnull可能と見なされます。これは、アンダーフローまたはオーバーフローによってnullの結果も生成されるためです。 AllowsNullプロパティを指定してCOLUMNPROPERTY関数を使用し、テーブル内の計算された列のnull可能性を調査します。 NULL可能である式は、ISNULL(check_expression、constant)を指定することにより、null不可の式に変換できます。ここで、constantは、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
これは今のところポジティブに見えますが、元の定義でさえこれらの2つのチェックから「NOT NULL」と報告されました。それでは、実際のテストを試してみましょう。データベースエンジンが実行時にnull可能性をどのように決定するかです。
SELECT * FROM sys.dm_exec_describe_first_result_set(N'SELECT * FROM dbo.Realty', '', NULL);
-- RankingFixed: is_nullable = 0 == :-) WOO HOO!
Ranking計算列式がどのような状況でも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
の使用に注意してください。