私は通常、次のルールに従ってデータベースを設計します。
CREATE TRIGGER <TriggerName>
ON <MyTable>
[BEFORE | AFTER] INSERT
AS
IF EXISTS (SELECT 1
FROM inserted
WHERE Field1 <> <some_initial_value>
OR Field2 <> <other_initial_value>)
BEGIN
UPDATE MyTable
SET Field1 = <some_initial_value>,
Field2 = <other_initial_value>
...
END
sp_MyTable_Insert(@Field1, @Field2, @Field3, ...);
sp_MyTable_Delete(@Key1, @Key2, ...);
sp_MyTable_Update(@Key1, @Key2, @Field3, ...);
このシナリオでは、DEFAULT CONSTRAINTを使用する価値があると思いますか、それともDBサーバーに余分な不要なジョブを追加していますか?
更新
DEFAULT制約を使用することで、データベースを管理する必要がある他のユーザーに詳細情報を提供していることを理解しています。しかし、私は主にパフォーマンスに興味があります。
正しい値を指定した場合でも、データベースは常にデフォルト値をチェックしていると想定しているため、同じジョブを2回実行しています。
たとえば、トリガーの実行内でDEFAULT制約を回避する方法はありますか?
正しい値を指定した場合でも、データベースは常にデフォルト値をチェックしていると想定しているため、同じ作業を2回実行しています。
ええと、なぜあなたはそれを仮定しますか? ;-)。デフォルトが存在し、それらが接続されている列がINSERT
ステートメントに存在しない場合に値を提供することを考えると、正反対のことを想定します:関連する列がINSERT
ステートメントに存在する場合、それらは完全に無視されます。
幸いなことに、私たちのどちらも問題のこの声明のために何も仮定する必要はありません:
私は主にパフォーマンスに興味があります。
パフォーマンスに関する質問は、ほとんどの場合テスト可能です。したがって、SQL Server(ここでは真の権限)がこの質問に回答できるようにするためのテストを考案する必要があります。
[〜#〜]セットアップ[〜#〜]
以下を1回実行します。
SET NOCOUNT ON;
-- DROP TABLE #HasDefault;
CREATE TABLE #HasDefault
(
[HasDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[SomeInt] INT NULL,
[SomeDate] DATETIME NOT NULL DEFAULT (GETDATE())
);
-- DROP TABLE #NoDefault;
CREATE TABLE #NoDefault
(
[NoDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[SomeInt] INT NULL,
[SomeDate] DATETIME NOT NULL
);
-- make sure that data file and Tran Log file are grown, if need be, ahead of time:
INSERT INTO #HasDefault ([SomeInt])
SELECT TOP (2000000) NULL
FROM [master].sys.[all_columns] ac1
CROSS JOIN [master].sys.[all_columns] ac2;
テスト1Aと1Bを一緒に実行するのではなく、個別に実行してください。それぞれを数回実行して、それぞれの平均タイミングを把握します。
テスト1A
TRUNCATE TABLE #HasDefault;
GO
PRINT '#HasDefault:';
SET STATISTICS TIME ON;
INSERT INTO #HasDefault ([SomeDate])
SELECT TOP (1000000) '2017-05-15 10:11:12.000'
FROM [master].sys.[all_columns] ac1
CROSS JOIN [master].sys.[all_columns] ac2;
SET STATISTICS TIME OFF;
GO
テスト1B
TRUNCATE TABLE #NoDefault;
GO
PRINT '#NoDefault:';
SET STATISTICS TIME ON;
INSERT INTO #NoDefault ([SomeDate])
SELECT TOP (1000000) '2017-05-15 10:11:12.000'
FROM [master].sys.[all_columns] ac1
CROSS JOIN [master].sys.[all_columns] ac2;
SET STATISTICS TIME OFF;
GO
テスト2Aと2Bを個別に実行します。タイミングがずれるため、一緒に実行しないでください。それぞれを数回実行して、それぞれの平均タイミングを把握します。
テスト2A
TRUNCATE TABLE #HasDefault;
GO
DECLARE @Counter INT = 0,
@StartTime DATETIME,
@EndTime DATETIME;
BEGIN TRAN;
--SET STATISTICS TIME ON;
SET @StartTime = GETDATE();
WHILE (@Counter < 100000)
BEGIN
INSERT INTO #HasDefault ([SomeDate]) VALUES ('2017-05-15 10:11:12.000');
SET @Counter = @Counter + 1;
END;
SET @EndTime = GETDATE();
--SET STATISTICS TIME OFF;
COMMIT TRAN;
PRINT DATEDIFF(MILLISECOND, @StartTime, @EndTime);
テスト2B
TRUNCATE TABLE #NoDefault;
GO
DECLARE @Counter INT = 0,
@StartTime DATETIME,
@EndTime DATETIME;
BEGIN TRAN;
--SET STATISTICS TIME ON;
SET @StartTime = GETDATE();
WHILE (@Counter < 100000)
BEGIN
INSERT INTO #NoDefault ([SomeDate]) VALUES ('2017-05-15 10:11:12.000');
SET @Counter = @Counter + 1;
END;
SET @EndTime = GETDATE();
--SET STATISTICS TIME OFF;
COMMIT TRAN;
PRINT DATEDIFF(MILLISECOND, @StartTime, @EndTime);
テスト1Aと1Bの間、またはテスト2Aと2Bの間のタイミングに実際の違いがないことがわかります。したがって、いいえ、DEFAULT
を定義しても使用しないことによるパフォーマンスの低下はありません。
また、意図した動作を単に文書化するだけでなく、DMLステートメントがストアドプロシージャ内に完全に含まれていることを気にするのは主にあなたであることに留意する必要があります。サポート担当者は気にしません。将来の開発者は、すべてのDMLをそれらのストアドプロシージャ内にカプセル化したいというあなたの望みを知らないかもしれませんし、知っていても気にしないかもしれません。そして、あなたが去った後(別のプロジェクトまたは仕事)にこのDBを保守している人は誰でも気にしないかもしれませんし、どんなに抗議してもORMの使用を妨げられないかもしれません。したがって、デフォルトは、INSERT
、特にサポート担当者が行う特別なINSERT
を実行するときに人々に「アウト」を与えるのに役立ちます。これは、含める必要のない1つの列であるためです(これは、常にデフォルトを使用する理由です)監査日付列)。
さらに、関連付けられた列がDEFAULT
ステートメントに存在するときにINSERT
がチェックされているかどうかを客観的に示すことができると思いました。無効な値を指定するだけです。次のテストはまさにそれを行います:
-- DROP TABLE #BadDefault;
CREATE TABLE #BadDefault
(
[BadDefaultID] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[SomeInt] INT NOT NULL DEFAULT (1 / 0)
);
INSERT INTO #BadDefault ([SomeInt]) VALUES (1234); -- Success!!!
SELECT * FROM #BadDefault; -- just to be sure ;-)
INSERT INTO #BadDefault ([SomeInt]) VALUES (DEFAULT); -- Error:
/*
Msg 8134, Level 16, State 1, Line xxxxx
Divide by zero error encountered.
The statement has been terminated.
*/
SELECT * FROM #BadDefault; -- just to be sure ;-)
GO
ご覧のとおり、列(およびキーワードDEFAULT
ではなく値)が指定されている場合、デフォルトは100%無視されます。これは、INSERT
が成功したためです。ただし、デフォルトを使用すると、最終的に実行されるときにエラーが発生します。
トリガーの実行内でDEFAULT制約を回避する方法はありますか?
デフォルトの制約を回避する必要は(少なくともこのコンテキストでは)完全に不要ですが、完全を期すために、INSTEAD OF
トリガー内のデフォルトの制約のみを「回避」することが可能であることに注意してください。 notAFTER
トリガー内。 CREATE TRIGGER のドキュメントによると:
トリガーテーブルに制約が存在する場合、それらはINSTEAD OFトリガーの実行後、AFTERトリガーの実行前にチェックされます。制約に違反すると、INSTEAD OFトリガーアクションがロールバックされ、AFTERトリガーは起動されません。
もちろん、INSTEAD OF
トリガーを使用するには、次のものが必要です。
AFTER
トリガーの作成ただし、これを行うことをお勧めしません。
デフォルトの制約があることに大きな害はないようです。実際、特定の利点が1つあります。テーブル定義自体と同じレベルのロジックでデフォルトを定義しました。ストアドプロシージャで提供するデフォルト値がある場合、誰かがそこに行ってデフォルト値を確認する必要があります。そして、それはシステムに新しい人にとって明白なことではありません(たとえば、明日10億ドルを相続し、自分の熱帯の島を購入してそこに移動し、他の貧しい樹液を残して物事を把握する場合)自分で出します)。