23

これは Azure にあります。

スーパータイプ エンティティといくつかのサブタイプ エンティティがあります。後者は、挿入ごとにスーパー タイプ エンティティの主キーから外部キーを取得する必要があります。Oracle では、BEFORE INSERTトリガーを使用してこれを実現します。SQL Server / T-SQLでこれをどのように達成しますか?

DDL

CREATE TABLE super (
 super_id int IDENTITY(1,1)
 ,subtype_discriminator char(4) CHECK (subtype_discriminator IN ('SUB1', 'SUB2')
 ,CONSTRAINT super_id_pk PRIMARY KEY (super_id)
);
CREATE TABLE sub1 (
 sub_id int IDENTITY(1,1)
,super_id int NOT NULL
,CONSTRAINT sub_id_pk PRIMARY KEY (sub_id)
,CONSTRAINT sub_super_id_fk FOREIGN KEY (super_id) REFERENCES super (super_id)
);

insert into が実際に値を挿入し、生成された to put intoを使用sub1するトリガーを起動することを望みます。supersuper_idsub1

Oracle では、これは次の方法で実現されます。

CREATE TRIGGER sub_trg
    BEFORE INSERT ON sub1
    FOR EACH ROW
DECLARE
    v_super_id int; //Ignore the fact that I could have used super_id_seq.CURRVAL
BEGIN
    INSERT INTO super (super_id, subtype_discriminator) 
        VALUES (super_id_seq.NEXTVAL, 'SUB1') 
        RETURNING super_id INTO v_super_id;
    :NEW.super_id := v_super_id;
END;

BEFORE INSERTT-SQLには機能がないため、T-SQLでこれをシミュレートする方法についてアドバイスしてください。

4

2 に答える 2

33

BEFOREトリガーをトリガーに置き換えることができる場合もありAFTERますが、挿入が行われる前に明らかに値を提供する必要があるため、これはあなたの状況には当てはまらないようです。そのため、@marc_s がコメントで示唆しているように、最も近い機能はINSTEAD OFトリガー機能のようです。

BEFOREただし、これら 2 つのトリガー タイプの名前が示すように、トリガーとトリガーには根本的な違いがあることに注意してくださいINSTEAD OF。どちらの場合も、トリガーを呼び出したステートメントによって決定されるアクションが発生していないときにトリガーが実行されますが、トリガーの場合INSTEAD OF、アクションはまったく発生しないはずです。実行する必要がある実際のアクションは、トリガー自体によって実行する必要があります。BEFOREこれは、明示的にロールバックしない限り、ステートメントが常に実行されるトリガー機能とは大きく異なります。

しかし、実際に取り組むべき問題がもう 1 つあります。Oracle スクリプトが明らかにしているように、変換する必要があるトリガーは、SQL Server でサポートされていない別の機能を使用していますFOR EACH ROW。SQL Server にも行ごとのトリガーはなく、ステートメントごとのトリガーのみです。つまり、挿入されたデータは単一の行ではなく、行セットであることに常に留意する必要があります。これにより複雑さが増しますが、説明する必要があるもののリストはおそらくこれで終わりです。

したがって、実際に解決すべきことは次の 2 つです。

  • 機能を置き換えBEFOREます。

  • 機能を置き換えFOR EACH ROWます。

これらを解決するための私の試みは以下のとおりです。

CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
  DECLARE @new_super TABLE (
    super_id int
  );
  INSERT INTO super (subtype_discriminator)
  OUTPUT INSERTED.super_id INTO @new_super (super_id)
  SELECT 'SUB1' FROM INSERTED;

  INSERT INTO sub (super_id)
  SELECT super_id FROM @new_super;
END;

上記の仕組みは次のとおりです。

  1. に挿入されるのと同じ数の行sub1が最初に に追加されsuperます。生成super_idされた値は、一時ストレージ ( と呼ばれるテーブル変数@new_super) に格納されます。

  2. 新しく挿入されたsuper_idが に挿入されsub1ます。

それほど難しいことはありませんが、上記はsub1、質問で指定した列以外に列がない場合にのみ機能します。他の列がある場合、上記のトリガーはもう少し複雑にする必要があります。

super_id問題は、挿入されたすべての行に個別に新しい s を割り当てることです。マッピングを実装する 1 つの方法は、次のようになります。

CREATE TRIGGER sub_trg
ON sub1
INSTEAD OF INSERT
AS
BEGIN
  DECLARE @new_super TABLE (
    rownum   int IDENTITY (1, 1),
    super_id int
  );
  INSERT INTO super (subtype_discriminator)
  OUTPUT INSERTED.super_id INTO @new_super (super_id)
  SELECT 'SUB1' FROM INSERTED;

  WITH enumerated AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rownum
    FROM inserted
  )
  INSERT INTO sub1 (super_id, other columns)
  SELECT n.super_id, i.other columns
  FROM enumerated AS i
  INNER JOIN @new_super AS n
  ON i.rownum = n.rownum;
END;

ご覧のとおり、IDENTIY(1,1)列が に追加される@new_userため、一時的に挿入されたsuper_id値が 1 から追加で列挙されます。新しいsuper_idと新しいデータ行の間のマッピングを提供するために、ROW_NUMBER関数を使用してINSERTED行も列挙します。その結果、INSERTEDセット内のすべての行を 1 つの行にリンクできるようになりsuper_id、完全なデータ行を補完して に挿入できるようになりましたsub1

新しいsuper_idが挿入される順序は、割り当てられる順序と一致しない場合があることに注意してください。私はそれを問題にしないと考えました。生成されたすべての新しいsuper行は、ID を除いて同一です。したがって、ここで必要なのは、新しい行super_idごとに新しいものを 1 つ取得することだけです。sub1

ただし、挿入のロジックsuperがより複雑で、何らかの理由で、どの新しい行super_idに対してどの新しい行が生成されたかを正確に覚えておく必要があるsub場合は、このスタック オーバーフローの質問で説明されているマッピング方法を検討することをお勧めします。

于 2013-03-17T20:42:36.427 に答える
2

Andriy の提案は少数のレコードの INSERT ではうまく機能しますが、「enumerated」と「@new_super」の両方がインデックス化されていないため、最終結合で完全なテーブル スキャンが実行され、大規模な挿入ではパフォーマンスが低下します。

これは、次のように @new_super テーブルに主キーを指定することで解決できます。

DECLARE @new_super TABLE (
  row_num INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
  super_id   int
);

これにより、SQL オプティマイザーは「列挙された」テーブルをスキャンしますが、新しいキーを取得するために @new_super でインデックス結合を実行します。

于 2017-01-12T18:49:26.253 に答える