トリガーでSELECT * okです。または私はトラブルを求めていますか?


8

私は仕事で議論に巻き込まれ、見落としている可能性のある落とし穴についてアドバイスが必要です。

削除されたレコードを監査テーブルにコピーするためにトリガーが使用されるシナリオを想像してみてください。トリガーはSELECT *を使用します。誰もが指さして叫び、これがどれほど悪いかを私たちに教えます。

ただし、メインテーブルの構造に変更が加えられ、監査テーブルが見落とされた場合、トリガーによってエラーが生成され、監査テーブルにも変更が必要であることを通知します。

このエラーは、DEVサーバーでのテスト中に検出されます。ただし、プロダクションがDEVと一致することを確認する必要があるため、プロダクションシステムでSELECT *を許可します(トリガーのみ)。

だから私の質問は:私はSELECT *を削除するようにプッシュされていますが、この性質の開発エラー、アイデア、またはこのベストプラクティスを自動的にキャプチャしていることを確認する他の方法がわかりませんか?

以下の例をまとめました:

--Create Test Table
CREATE TABLE dbo.Test(ID INT IDENTITY(1,1), Person VARCHAR(255))
--Create Test Audit Table
CREATE TABLE dbo.TestAudit(AuditID INT IDENTITY(1,1),ID INT, Person VARCHAR(255))

--Create Trigger on Test
CREATE TRIGGER [dbo].[trTestDelete] ON [dbo].[Test] AFTER DELETE
NOT FOR REPLICATION
AS
BEGIN
    SET NOCOUNT ON;
    INSERT  dbo.TestAudit([ID], [Person])
    SELECT  *
    FROM    deleted
END

--Insert Test Data into Test
INSERT INTO dbo.Test VALUES
('Scooby')
,('Fred')
,('Shaggy')

--Perform a delete
DELETE dbo.Test WHERE Person = 'Scooby'

更新(言い換え質問):

私はDBAであり、ベストプラクティスドキュメントに貢献することで、開発者が十分に考え抜かれた展開スクリプトを提供しないようにする必要があります。SELECT *は、開発者が監査テーブル(これはセーフティネットです)を見落とすと、DEVでエラーが発生するため、開発プロセスの早い段階でエラーがキャッチされます。しかし、SQL憲法のどこかで-2番目の改正では、「あなたはSELECT *を使わないでください」と書かれています。そのため、セーフティネットを排除する必要があります。

セーフティネットをどのように置き換えますか、またはこれをトリガーのベストプラクティスと見なす必要がありますか?

更新2:(解決策)

たくさんのご意見ありがとうございました。これは非常に灰色の主題であるように思われるため、明確な答えがあるかどうかはわかりません。しかし、まとめて、開発者がベストプラクティスの定義を進めるのに役立つディスカッションポイントを提供しました。

Daevinあなたの貢献に感謝します。あなたの答えは、開発者が実装できるいくつかのテストメカニズムの基礎を提供します。+1

おかげでCM_Dayton、ベストプラクティスに貢献する提案は、監査トリガーを開発しているすべての人にとって有益です。+1

おかげでypercube、さまざまな形式の定義変更が行われるテーブルに関する問題について多くのことを考えました。+1

結論として:

Is Select * ok in a tigger? はい、それは灰色の領域です。「選択*は悪い」というイデオロギーを盲目的に実行しないでください。

Am I asking for Trouble? はい、テーブルに新しい列を追加するだけではありません。


質問に答えます。ソーステーブルが変更されると、select *は機能しなくなります。devとprodが同じであることを確認するには、何らかの形式のソース管理を使用します。
Bob Klimes 2017

少し広い質問ですが、レコードを削除する頻度とテーブルの合計パーセンテージはいくつですか。トリガーの代わりに、行を削除済みとしてマークするビットフラグと、ログテーブルに移動するためにスケジュールに従って実行されるエージェントジョブがあります。エージェントのジョブチェックに組み込み、テーブルスキーマが一致するかどうかを確認できます。修正されるまで、そのステップに問題がある場合、ジョブは単に失敗します。
タナー

私はいつもSELECT *怠惰であることに同意しますが、あなたがそれを使用する正当な理由があるので、それは白黒よりも灰色です。あなたがやるべきことはこのようなものですが、同じ列数を持つだけでなく、列名とデータ型が同じになるように調整してください(誰かがデータ型を変更しても、通常はキャッチされないdbで問題を引き起こす可能性があるため)あなたとSELECT *。「セーフティネット」
Daevin

3
SELECT *セーフティネットとして使用するのは好きですが、すべてのケースに対応できるわけではありません。たとえば、列を削除して再度追加した場合などです。これにより、列の順序が変更され、(すべての列が同じタイプでない限り)監査テーブルへの挿入が失敗するか、暗黙的なタイプ変換によりデータが失われます。
ypercubeᵀᴹ

2
また、列がテーブルから削除されたときに、監査設計がどのように機能するのかについても疑問です。また、列を監査テーブルから削除しますか(そして以前のすべての監査データを失います)?
ypercubeᵀᴹ

回答:


10

通常、これは「遅延」プログラミングと見なされます。

ここではテーブルに2つの値を具体的に挿入TestAuditしているので、selectでも正確に2つの値が取得されるように注意します。なんらかの理由で、そのTestテーブルに3番目の列がある場合、または3番目の列がある場合、このトリガーは失敗します。

あなたの質問に直接関係はありませんが、監査テーブルを設定している場合は、テーブルにいくつかの列を追加TestAuditして...

  • 監査しているアクションを追跡します(この場合は削除か、挿入または更新か)。
  • 監査イベントがいつ発生したかを追跡する日付/時刻列
  • 監査しているアクションを実行したユーザーを追跡するユーザーID列。

したがって、次のようなクエリになります。

INSERT dbo.TestAudit([ID], [Person], [AuditAction], [ChangedOn], [ChangedBy])
SELECT [ID], [Person], 
   'Delete', -- or a 'D' or a numeric lookup to an audit actions table...
   GetDate(), -- or SYSDATETIME() for greater precision
   SYSTEM_USER -- or some other value for WHO made the deletion
FROM deleted

このようにして、必要な正確な列を取得し、監査イベントの内容/時期/理由/対象者を監査します。


「ユーザーID」これは監査には注意が必要です。通常、データベースアカウントは実際のユーザーに対応していません。多くの場合、それらは単一のWebアプリケーションまたはその他の種類のコンポーネントに対応し、そのコンポーネントで使用される資格情報の単一のセットを備えています。(そして、コンポーネントが資格情報を共有することもあります。)したがって、データベースの資格情報は、どのコンポーネントがそれを実行したかに興味がない限り、誰が何を実行したかの識別子としてはほとんど役に立ちません。しかし、「誰」を識別するアプリケーションデータを渡すことは、私が知る限り、トリガー関数を使用して簡単に行うことはできません。
jpmc26 2017

質問の更新を参照してください。
pacreely 2017

SELECT *が一般的に発生する可能性のあるもう1つの問題は(おそらく例ではそうではありませんが)、基になるテーブルの列が挿入列と同じ順序でない場合、挿入が失敗することです。
CaM 2017

3

私はあなたの質問にこれについてコメントしましたが、私は実際にコードソリューションを提示しようとすると思いました。

私はいつもSELECT *怠惰であることに同意しますが、あなたがそれを使う正当な理由があるので、それは白黒よりも灰色です。

あなたがすべき(私の意見では)やってみてくださいのようなもので、これは、誰かが正常にあなたを捕まえていないデシベルで問題原因はまだデータ型を変更し、可能性があるため、列名とデータ型は、(同じであることを確認するために、それを調整するSELECT *の安全ネット'。

テーブルの監査バージョンが非監査バージョンと一致するかどうかをすばやく確認できる関数を作成することもできます。

-- The lengths are, I believe, max values for the corresponding db objects. If I'm wrong, someone please correct me
CREATE FUNCTION TableMappingComparer(
    @TableCatalog VARCHAR(85) = NULL,
    @TableSchema VARCHAR(32) = NULL,
    @TableName VARCHAR(128) = NULL) RETURNS BIT
AS
BEGIN
    DECLARE @ReturnValue BIT = NULL;
    DECLARE @VaryingColumns INT = NULL;

    IF (@TableCatalog IS NOT NULL
            AND @TableSchema IS NOT NULL
            AND @TableName IS NOT NULL)
        SELECT @VaryingColumns = COUNT(COLUMN_NAME)
            FROM (SELECT COLUMN_NAME,
                        DATA_TYPE -- Add more columns that you want to ensure are identical
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_CATALOG = @TableCatalog
                        AND TABLE_SCHEMA = @TableSchema
                        AND TABLE_NAME = @TableName
                EXCEPT
                    SELECT COLUMN_NAME,
                            DATA_TYPE -- Add more columns that you want to ensure are identical
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE (TABLE_CATALOG = @TableCatalog
                            AND TABLE_SCHEMA = @TableSchema
                            AND TABLE_NAME = @TableName + 'Audit')
                            AND (COLUMN_NAME != 'exclude your audit table specific columns')) adt;
    IF @VaryingColumns = 0
        SET @ReturnValue = 1
    ELSE IF @VaryingColumns > 0
        SET @ReturnValue = 0

    RETURN @ReturnValue;
END;

SELECT ... EXCEPT SELECT ...Auditテーブルの列は、監査テーブルにない何が表示されます。マップするかどうかだけでなく、同じではない列の名前を返すように関数を変更したり、例外を発生させたりすることもできます。

あなたは、その後から移動する前にこれを実行することができますDEVPRODUCTION、DBのテーブルごとに、サーバ上にカーソルを使用しました:

SELECT TABLE_NAME
    FROM INFORMATION_SCHEMA.TABLES
    WHERE NOT (TABLE_NAME LIKE '%Audit')

1
質問の更新を参照してください
pacreely 2017

お役に立てて嬉しいです。すべての回答を読み、それらをチームに持ち帰って提案してくれたことに対するあなたの功績; 適応性と改善意欲は、技術部門が企業を運営し、スムーズに運営する方法です。:D
Daevin

0

トリガーをキューに入れるステートメントは失敗し、トリガーは失敗します。トリガーと監査証跡を文書化して、*を指定する代わりにクエリを変更して列を追加することをお勧めします。

少なくともトリガーを変更して、エラーをテーブルに記録しているときに正常に失敗し、トリガーがエラーを記録しているテーブルにアラートを設定する必要があります。

これは、誰かがテーブルを変更して列を追加したり列を削除したりしたときにトリガーまたはアラートを出し、トリガーを追加するよう通知することもできます。

パフォーマンスに関しては、*は何も変わらないと信じています。物事が変わったときに障害が発生する可能性が高まるだけでなく、必要なときにネットワーク経由でより多くの情報を取得すると、ネットワーク遅延が発生する可能性があります。*には時間と場所がありますが、上記のように、代わりに試すためのより良いソリューションとツールがあると思います。


0

元のテーブル構造または監査テーブル構造がまったく変更されている場合は、select *で問題が発生することを保証しています。

INSERT INTO [AuditTable]
(Col1,Col2,Col3)
SELECT * 
FROM [OrigTable] or [deleted];

いずれかが変更されると、トリガーはエラーになります。

あなたがすることができます:

INSERT INTO [AuditTable]
SELECT * 
FROM [OrigTable];

しかし、CM_Daytonが言うように、それは遅延プログラミングであり、他の不整合の扉を開きます。このシナリオが機能するためには、両方のテーブルの構造を同時に更新していることを完全に確認する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.