C'est sur Azure.
J'ai une entité de supertype et plusieurs entités de sous-type, dont ces dernières doivent obtenir leurs clés étrangères à partir de la clé primaire de l'entité de super type sur chaque insert. Dans Oracle, j'utilise un BEFORE INSERT
déclencheur pour accomplir cela. Comment accomplir cela dans 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)
);
Je souhaite un insert dans sub1
pour déclencher un déclencheur qui insère réellement une valeur dans super
et utilise le super_id
généré pour mettre dans sub1
.
Dans Oracle, cela serait accompli comme suit:
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;
Veuillez indiquer comment je simulerais cela dans T-SQL, étant donné que T-SQL n'a pas le BEFORE INSERT
aptitude?
Parfois, un déclencheur BEFORE
peut être remplacé par un déclencheur AFTER
, mais cela ne semble pas être le cas dans votre situation, car vous devez clairement fournir une valeur avant l'insertion. Donc, à cet effet, la fonctionnalité la plus proche semble être celle du déclencheur INSTEAD OF
, Comme @ marc_s l'a suggéré dans son commentaire.
Notez cependant que, comme le suggèrent les noms de ces deux types de déclencheurs, il existe une différence fondamentale entre un déclencheur BEFORE
et un déclencheur INSTEAD OF
. Alors que dans les deux cas le déclencheur est exécuté au moment où l'action déterminée par l'instruction qui a été invoquée le déclencheur n'a pas eu lieu, dans le cas du déclencheur INSTEAD OF
L'action est n'a jamais supposé avoir lieu du tout. L'action réelle que vous devez effectuer doit être effectuée par le déclencheur lui-même . Ceci est très différent de la fonctionnalité de déclenchement BEFORE
, où l'instruction doit toujours être exécutée, sauf, bien sûr, si vous la restaurez explicitement.
Mais il y a un autre problème à résoudre en fait. Comme le révèle votre script Oracle, le déclencheur que vous devez convertir utilise une autre fonctionnalité non prise en charge par SQL Server, qui est celle de FOR EACH ROW
. Il n'y a pas non plus de déclencheurs par ligne dans SQL Server, uniquement des déclencheurs par instruction. Cela signifie que vous devez toujours garder à l'esprit que les données insérées sont une ligne définie , pas seulement une seule ligne. Cela ajoute plus de complexité, bien que cela conclura probablement la liste des choses dont vous devez tenir compte.
Donc, c'est vraiment deux choses à résoudre:
remplacer la fonctionnalité BEFORE
;
remplacez la fonctionnalité FOR EACH ROW
.
Ma tentative de résoudre ces problèmes est ci-dessous:
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;
Voici comment fonctionne ce qui précède:
Le même nombre de lignes que celui inséré dans sub1
Est d'abord ajouté à super
. Les valeurs super_id
Générées sont stockées dans un stockage temporaire (une variable de table appelée @new_super
).
Les super_id
Nouvellement insérés sont maintenant insérés dans sub1
.
Rien de trop difficile, mais ce qui précède ne fonctionnera que si vous n'avez pas d'autres colonnes dans sub1
Que celles que vous avez spécifiées dans votre question. S'il y a d'autres colonnes, le déclencheur ci-dessus devra être un peu plus complexe.
Le problème est d'affecter les nouveaux super_id
À chaque ligne insérée individuellement. Une façon de mettre en œuvre le mappage pourrait être comme ci-dessous:
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;
Comme vous pouvez le voir, une colonne IDENTIY(1,1)
est ajoutée à @new_user
, Donc les valeurs super_id
Insérées temporairement seront en plus énumérées à partir de 1. Pour fournir le mappage entre le nouveau super_id
S et les nouvelles lignes de données, la fonction ROW_NUMBER
Est également utilisée pour énumérer les lignes INSERTED
. Par conséquent, chaque ligne de l'ensemble INSERTED
peut désormais être liée à un seul super_id
Et donc complétée par une ligne de données complète à insérer dans sub1
.
Notez que l'ordre dans lequel les nouveaux super_id
Sont insérés peut ne pas correspondre à l'ordre dans lequel ils sont attribués. J'ai considéré que ce n'était pas un problème. Toutes les nouvelles lignes super
générées sont identiques sauf pour les ID. Donc, tout ce dont vous avez besoin ici est de prendre une nouvelle super_id
Par nouvelle ligne sub1
.
Cependant, si la logique d'insertion dans super
est plus complexe et pour une raison quelconque, vous devez vous rappeler précisément quelle nouvelle super_id
A été générée pour quelle nouvelle ligne sub
, vous 'll voudra probablement considérer la méthode de mappage discutée dans cette question de débordement de pile:
Alors que la proposition d'Andriy fonctionnera bien pour les INSERT d'un petit nombre d'enregistrements, des analyses complètes de la table seront effectuées sur la jointure finale car `` énumérées '' et `` @new_super '' ne sont pas indexées, ce qui entraîne de mauvaises performances pour les insertions volumineuses.
Cela peut être résolu en spécifiant une clé primaire sur la table @new_super, comme suit:
DECLARE @new_super TABLE (
row_num INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
super_id int
);
Cela se traduira par l'optimiseur SQL analysant la table "énumérée" mais effectuant une jointure indexée sur @new_super pour obtenir la nouvelle clé.