これは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)
);
sub1
に挿入して、super
に実際に値を挿入し、super_id
に入れるために生成されたsub1
を使用するトリガーを起動することを希望します。
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;
T-SQLにBEFORE INSERT
機能がない場合、T-SQLでこれをどのようにシミュレートするかアドバイスしてください。
時にはBEFORE
トリガーをAFTER
トリガーに置き換えることもできますが、挿入が行われる前に値を提供する必要があるため、状況によってはそうではないようです。そのため、 @marc_sが彼のコメントで を示唆しているように、最も近い機能は_INSTEAD OF
_トリガー1のようです。
ただし、これら2つのトリガータイプの名前が示すように、BEFORE
トリガーと_INSTEAD OF
_トリガーには根本的な違いがあることに注意してください。どちらの場合も、トリガーが呼び出されたステートメントによって決定されたアクションが実行されていないときにトリガーが実行されますが、_INSTEAD OF
_トリガーの場合、アクションは想定されませんまったく行われます。実行する必要がある実際のアクションは、トリガー自体によって実行する必要があります。これは、もちろん明示的にロールバックしない限り、ステートメントが常に実行されるためのBEFORE
トリガー機能とは非常に異なります。
しかし、実際に対処すべき問題がもう1つあります。 Oracleスクリプトが示すように、変換する必要があるトリガーは、SQL Serverでサポートされていない別の機能、_FOR EACH ROW
_を使用します。 SQL Serverにも行ごとのトリガーはなく、ステートメントごとのトリガーのみがあります。つまり、挿入されたデータは単一の行ではなく、行setであることに常に留意する必要があります。これにより複雑さが増しますが、おそらく、説明する必要のあるもののリストはおしまいです。
そのため、実際に解決する必要があるのは次の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;
_
これが上記の仕組みです:
_sub1
_に挿入されるのと同じ行数が最初にsuper
に追加されます。生成された_super_id
_値は、一時ストレージ(_@new_super
_と呼ばれるテーブル変数)に保存されます。
新しく挿入された_super_id
_ sが_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
_ sおよび新しいデータ行、_ROW_NUMBER
_関数を使用してINSERTED
行も列挙します。その結果、INSERTED
セットのすべての行を単一の_super_id
_にリンクできるようになり、_sub1
_に挿入される完全なデータ行を補完できるようになりました。
新しい_super_id
_ sが挿入される順序は、割り当てられた順序と一致しない場合があることに注意してください。私はそれを問題ではないと考えました。生成されたすべての新しいsuper
行は、IDの保存と同じです。したがって、ここで必要なのは、新しい_super_id
_行ごとに1つの新しい_sub1
_を取得するだけです。
ただし、super
に挿入するロジックがより複雑で、何らかの理由で、どの新しいsub
行に対してどの新しい_super_id
_が生成されたかを正確に覚えておく必要がある場合、おそらく、このStack Overflowの質問で説明されているマッピング方法を検討したいと思うでしょう。
Andriyの提案は少数のレコードのINSERTでうまく機能しますが、 'enumerated'と '@new_super'の両方がインデックス付けされていないため、最終的な結合でテーブル全体のスキャンが行われ、大きな挿入のパフォーマンスが低下します。
これは、次のように@new_superテーブルで主キーを指定することで解決できます。
DECLARE @new_super TABLE (
row_num INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
super_id int
);
これにより、SQLオプティマイザーは 'enumerated'テーブルをスキャンしますが、@ new_superでインデックス付き結合を実行して新しいキーを取得します。