J'ai besoin d'écrire un déclencheur d'insertion et de mise à jour sur la table A qui supprimera toutes les lignes de la table B dont une colonne (par exemple Desc) a des valeurs similaires à celle insérée/mise à jour dans la colonne de la table A (disons Col1). Comment pourrais-je l'écrire pour pouvoir traiter les cas Update et Insert. Comment pourrais-je déterminer si le déclencheur est exécuté pour une mise à jour ou une insertion?.
Si c'est MS SQL Server ...
Les déclencheurs disposent de tables INSERTED
et DELETED
spéciales pour suivre les données "avant" et "après". Vous pouvez donc utiliser quelque chose comme IF EXISTS (SELECT * FROM DELETED)
pour détecter une mise à jour. Vous ne disposez que de lignes dans DELETED
lors de la mise à jour, mais il existe toujours des lignes dans INSERTED
.
Recherchez "inséré" dans CREATE TRIGGER
Edit, 23 nov. 2011
Après commentaire, cette réponse concerne uniquement les déclencheurs INSERTED
et UPDATED
.
Évidemment, les déclencheurs DELETE ne peuvent pas avoir "toujours des lignes dans INSERTED
" comme je l'ai dit plus haut
CREATE TRIGGER dbo.TableName_IUD
ON dbo.TableName
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
--
-- Check if this is an INSERT, UPDATE or DELETE Action.
--
DECLARE @action as char(1);
SET @action = 'I'; -- Set Action to Insert by default.
IF EXISTS(SELECT * FROM DELETED)
BEGIN
SET @action =
CASE
WHEN EXISTS(SELECT * FROM INSERTED) THEN 'U' -- Set Action to Updated.
ELSE 'D' -- Set Action to Deleted.
END
END
ELSE
IF NOT EXISTS(SELECT * FROM INSERTED) RETURN; -- Nothing updated or inserted.
...
END
Nombre de ces suggestions ne sont pas prises en compte si vous exécutez une instruction delete qui ne supprime rien.
Supposons que vous tentiez de supprimer un identifiant égal à une valeur inexistante dans la table.
Votre déclencheur est toujours appelé mais il n’ya rien dans les tables supprimées ou insérées.
Utilisez ceci pour être sûr:
--Determine if this is an INSERT,UPDATE, or DELETE Action or a "failed delete".
DECLARE @Action as char(1);
SET @Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED)
AND EXISTS(SELECT * FROM DELETED)
THEN 'U' -- Set Action to Updated.
WHEN EXISTS(SELECT * FROM INSERTED)
THEN 'I' -- Set Action to Insert.
WHEN EXISTS(SELECT * FROM DELETED)
THEN 'D' -- Set Action to Deleted.
ELSE NULL -- Skip. It may have been a "failed delete".
END)
Un merci spécial à @KenDog et @Net_Prog pour leurs réponses.
J'ai construit cela à partir de leurs scripts.
J'utilise ce qui suit, il détecte également correctement les instructions delete qui ne suppriment rien:
CREATE TRIGGER dbo.TR_TableName_TriggerName
ON dbo.TableName
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS(SELECT * FROM INSERTED)
-- DELETE
PRINT 'DELETE';
ELSE
BEGIN
IF NOT EXISTS(SELECT * FROM DELETED)
-- INSERT
PRINT 'INSERT';
ELSE
-- UPDATE
PRINT 'UPDATE';
END
END;
Après de nombreuses recherches, je n'ai pas pu trouver un exemple exact d'un déclencheur SQL Server unique qui gère les trois (3) conditions des actions de déclencheur INSERT, UPDATE et DELETE. J'ai finalement trouvé une ligne de texte qui parlait du fait que lorsqu'un tableau DELETE ou UPDATE se produisait, la table commune DELETED contiendrait un enregistrement pour ces deux actions. Sur la base de ces informations, j'ai ensuite créé une petite routine Action qui détermine la raison pour laquelle le déclencheur a été activé. Ce type d'interface est parfois nécessaire lorsqu'il existe à la fois une configuration commune et une action spécifique à exécuter sur un déclencheur INSERT vs. UPDATE. Dans ces cas, créer un déclencheur distinct pour UPDATE et INSERT deviendrait un problème de maintenance. (c.-à-d. les deux déclencheurs ont-ils été mis à jour correctement pour le correctif d'algorithme de données commun nécessaire?)
À cette fin, j'aimerais donner l'extrait de code d'événement à déclenchements multiples suivant pour la gestion des actions INSERT, UPDATE, DELETE dans un déclencheur pour Microsoft SQL Server.
CREATE TRIGGER [dbo].[INSUPDDEL_MyDataTable]
ON [dbo].[MyDataTable] FOR INSERT, UPDATE, DELETE
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with caller queries SELECT statements.
-- If an update/insert/delete occurs on the main table, the number of records affected
-- should only be based on that table and not what records the triggers may/may not
-- select.
SET NOCOUNT ON;
--
-- Variables Needed for this Trigger
--
DECLARE @PACKLIST_ID varchar(15)
DECLARE @LINE_NO smallint
DECLARE @SHIPPED_QTY decimal(14,4)
DECLARE @CUST_ORDER_ID varchar(15)
--
-- Determine if this is an INSERT,UPDATE, or DELETE Action
--
DECLARE @Action as char(1)
DECLARE @Count as int
SET @Action = 'I' -- Set Action to 'I'nsert by default.
SELECT @Count = COUNT(*) FROM DELETED
if @Count > 0
BEGIN
SET @Action = 'D' -- Set Action to 'D'eleted.
SELECT @Count = COUNT(*) FROM INSERTED
IF @Count > 0
SET @Action = 'U' -- Set Action to 'U'pdated.
END
if @Action = 'D'
-- This is a DELETE Record Action
--
BEGIN
SELECT @PACKLIST_ID =[PACKLIST_ID]
,@LINE_NO = [LINE_NO]
FROM DELETED
DELETE [dbo].[MyDataTable]
WHERE [PACKLIST_ID]=@PACKLIST_ID AND [LINE_NO]=@LINE_NO
END
Else
BEGIN
--
-- Table INSERTED is common to both the INSERT, UPDATE trigger
--
SELECT @PACKLIST_ID =[PACKLIST_ID]
,@LINE_NO = [LINE_NO]
,@SHIPPED_QTY =[SHIPPED_QTY]
,@CUST_ORDER_ID = [CUST_ORDER_ID]
FROM INSERTED
if @Action = 'I'
-- This is an Insert Record Action
--
BEGIN
INSERT INTO [MyChildTable]
(([PACKLIST_ID]
,[LINE_NO]
,[STATUS]
VALUES
(@PACKLIST_ID
,@LINE_NO
,'New Record'
)
END
else
-- This is an Update Record Action
--
BEGIN
UPDATE [MyChildTable]
SET [PACKLIST_ID] = @PACKLIST_ID
,[LINE_NO] = @LINE_NO
,[STATUS]='Update Record'
WHERE [PACKLIST_ID]=@PACKLIST_ID AND [LINE_NO]=@LINE_NO
END
END
Je crois que niché est un peu déroutant et:
Flat est mieux que niché [The Zen of Python]
;)
DROP TRIGGER IF EXISTS AFTER_MYTABLE
GO
CREATE TRIGGER dbo.AFTER_MYTABLE ON dbo.MYTABLE AFTER INSERT, UPDATE, DELETE
AS BEGIN
--- FILL THE BEGIN/END SECTION FOR YOUR NEEDS.
SET NOCOUNT ON;
IF EXISTS(SELECT * FROM INSERTED) AND EXISTS(SELECT * FROM DELETED)
BEGIN PRINT 'UPDATE' END
ELSE IF EXISTS(SELECT * FROM INSERTED) AND NOT EXISTS(SELECT * FROM DELETED)
BEGIN PRINT 'INSERT' END
ELSE IF EXISTS(SELECT * FROM DELETED) AND NOT EXISTS(SELECT * FROM INSERTED)
BEGIN PRINT 'DELETED' END
ELSE BEGIN PRINT 'NOTHING CHANGED'; RETURN; END -- NOTHING
END
Declare @Type varchar(50)='';
IF EXISTS (SELECT * FROM inserted) and EXISTS (SELECT * FROM deleted)
BEGIN
SELECT @Type = 'UPDATE'
END
ELSE IF EXISTS(SELECT * FROM inserted)
BEGIN
SELECT @Type = 'INSERT'
END
ElSE IF EXISTS(SELECT * FROM deleted)
BEGIN
SELECT @Type = 'DELETE'
END
Essaye ça..
ALTER TRIGGER ImportacionesGS ON dbo.Compra
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
-- idCompra is PK
DECLARE @vIdCompra_Ins INT,@vIdCompra_Del INT
SELECT @vIdCompra_Ins=Inserted.idCompra FROM Inserted
SELECT @vIdCompra_Del=Deleted.idCompra FROM Deleted
IF (@vIdCompra_Ins IS NOT NULL AND @vIdCompra_Del IS NULL)
Begin
-- Todo Insert
End
IF (@vIdCompra_Ins IS NOT NULL AND @vIdCompra_Del IS NOT NULL)
Begin
-- Todo Update
End
IF (@vIdCompra_Ins IS NULL AND @vIdCompra_Del IS NOT NULL)
Begin
-- Todo Delete
End
END
Cela pourrait être un moyen plus rapide:
DECLARE @action char(1)
IF COLUMNS_UPDATED() > 0 -- insert or update
BEGIN
IF EXISTS (SELECT * FROM DELETED) -- update
SET @action = 'U'
ELSE
SET @action = 'I'
END
ELSE -- delete
SET @action = 'D'
Un problème potentiel avec les deux solutions proposées est que, selon la manière dont elles sont écrites, une requête de mise à jour peut mettre à jour zéro enregistrement et une requête d'insertion peut insérer zéro enregistrement. Dans ces cas, les jeux d'enregistrements insérés et supprimés seront vides. Dans de nombreux cas, si les jeux d'enregistrements insérés et supprimés sont vides, vous voudrez peut-être simplement quitter le déclencheur sans rien faire.
bien que j'aime aussi la réponse publiée par @Alex, j'offre cette variante à la solution de @ Graham ci-dessus.
ceci utilise exclusivement l'existence d'enregistrement dans les tables INSERTED et UPDATED, par opposition à l'utilisation de COLUMNS_UPDATED pour le premier test. Il fournit également au programmeur un soulagement paranoïaque sachant que le cas final a été examiné ...
declare @action varchar(4)
IF EXISTS (SELECT * FROM INSERTED)
BEGIN
IF EXISTS (SELECT * FROM DELETED)
SET @action = 'U' -- update
ELSE
SET @action = 'I' --insert
END
ELSE IF EXISTS (SELECT * FROM DELETED)
SET @action = 'D' -- delete
else
set @action = 'noop' --no records affected
--print @action
vous obtiendrez NOOP avec une déclaration comme celle-ci:
update tbl1 set col1='cat' where 1=2
J'ai trouvé une petite erreur dans la solution Grahams autrement cool:
Ce doit être IF COLUMNS_UPDATED () <> 0 - insérer ou mettre à jour
au lieu de> 0 probablement parce que le bit supérieur est interprété comme un bit de signe entier SIGNÉ ... (?). Donc au total:
DECLARE @action CHAR(8)
IF COLUMNS_UPDATED() <> 0 -- delete or update?
BEGIN
IF EXISTS (SELECT * FROM deleted) -- updated cols + old rows means action=update
SET @action = 'UPDATE'
ELSE
SET @action = 'INSERT' -- updated columns and nothing deleted means action=insert
END
ELSE -- delete
BEGIN
SET @action = 'DELETE'
END
declare @insCount int
declare @delCount int
declare @action char(1)
select @insCount = count(*) from INSERTED
select @delCount = count(*) from DELETED
if(@insCount > 0 or @delCount > 0)--if something was actually affected, otherwise do nothing
Begin
if(@insCount = @delCount)
set @action = 'U'--is update
else if(@insCount > 0)
set @action = 'I' --is insert
else
set @action = 'D' --is delete
--do stuff here
End
Cela fait le tour pour moi:
declare @action_type int;
select @action_type = case
when i.id is not null and d.id is null then 1 -- insert
when i.id is not null and d.id is not null then 2 -- update
when i.id is null and d.id is not null then 3 -- delete
end
from inserted i
full join deleted d on d.id = i.id
Étant donné que toutes les colonnes ne peuvent pas être mises à jour à la fois, vous pouvez vérifier si une colonne particulière est mise à jour de la manière suivante:
IF UPDATE([column_name])
J'aime les solutions "élégantes en informatique". Ma solution ici utilise les pseudotables [inséré] et [supprimé] une fois chacun pour obtenir leurs statuts et place le résultat dans une variable à mappage binaire. Ensuite, chaque combinaison possible de INSERT, UPDATE et DELETE peut être facilement testée tout au long du déclencheur avec des évaluations binaires efficaces (à l'exception de la combinaison improbable INSERT ou DELETE).
Cela suppose que l’instruction DML importe peu si aucune ligne n’est modifiée (ce qui devrait satisfaire la grande majorité des cas). Ainsi, même si elle n’est pas aussi complète que la solution de Roman Pekar, elle est plus efficace.
Avec cette approche, nous avons la possibilité d’un déclencheur "FOR INSERT, UPDATE, DELETE" par table, nous donnant A) un contrôle complet sur l’ordre d’action et b) une implémentation de code par action applicable multi-action. (Évidemment, chaque modèle de mise en œuvre a ses avantages et ses inconvénients; vous devrez évaluer vos systèmes individuellement pour déterminer ce qui fonctionne vraiment le mieux.)
Notez que les instructions "existe (sélectionnez * dans" inséré/supprimé ")" sont très efficaces puisqu'il n'y a pas d'accès au disque ( https://social.msdn.Microsoft.com/Forums/en-US/01744422 -23fe-42f6-9ab0-a255cdf2904a ).
use tempdb
;
create table dbo.TrigAction (asdf int)
;
GO
create trigger dbo.TrigActionTrig
on dbo.TrigAction
for INSERT, UPDATE, DELETE
as
declare @Action tinyint
;
-- Create bit map in @Action using bitwise OR "|"
set @Action = (-- 1: INSERT, 2: DELETE, 3: UPDATE, 0: No Rows Modified
(select case when exists (select * from inserted) then 1 else 0 end)
| (select case when exists (select * from deleted ) then 2 else 0 end))
;
-- 21 <- Binary bit values
-- 00 -> No Rows Modified
-- 01 -> INSERT -- INSERT and UPDATE have the 1 bit set
-- 11 -> UPDATE <
-- 10 -> DELETE -- DELETE and UPDATE have the 2 bit set
raiserror(N'@Action = %d', 10, 1, @Action) with nowait
;
if (@Action = 0) raiserror(N'No Data Modified.', 10, 1) with nowait
;
-- do things for INSERT only
if (@Action = 1) raiserror(N'Only for INSERT.', 10, 1) with nowait
;
-- do things for UPDATE only
if (@Action = 3) raiserror(N'Only for UPDATE.', 10, 1) with nowait
;
-- do things for DELETE only
if (@Action = 2) raiserror(N'Only for DELETE.', 10, 1) with nowait
;
-- do things for INSERT or UPDATE
if (@Action & 1 = 1) raiserror(N'For INSERT or UPDATE.', 10, 1) with nowait
;
-- do things for UPDATE or DELETE
if (@Action & 2 = 2) raiserror(N'For UPDATE or DELETE.', 10, 1) with nowait
;
-- do things for INSERT or DELETE (unlikely)
if (@Action in (1,2)) raiserror(N'For INSERT or DELETE.', 10, 1) with nowait
-- if already "return" on @Action = 0, then use @Action < 3 for INSERT or DELETE
;
GO
set nocount on;
raiserror(N'
INSERT 0...', 10, 1) with nowait;
insert dbo.TrigAction (asdf) select top 0 object_id from sys.objects;
raiserror(N'
INSERT 3...', 10, 1) with nowait;
insert dbo.TrigAction (asdf) select top 3 object_id from sys.objects;
raiserror(N'
UPDATE 0...', 10, 1) with nowait;
update t set asdf = asdf /1 from dbo.TrigAction t where asdf <> asdf;
raiserror(N'
UPDATE 3...', 10, 1) with nowait;
update t set asdf = asdf /1 from dbo.TrigAction t;
raiserror(N'
DELETE 0...', 10, 1) with nowait;
delete t from dbo.TrigAction t where asdf < 0;
raiserror(N'
DELETE 3...', 10, 1) with nowait;
delete t from dbo.TrigAction t;
GO
drop table dbo.TrigAction
;
GO
Dans le premier scénario, je suppose que votre table a la colonne IDENTITY
CREATE TRIGGER [dbo].[insupddel_yourTable] ON [yourTable]
FOR INSERT, UPDATE, DELETE
AS
IF @@ROWCOUNT = 0 return
SET NOCOUNT ON;
DECLARE @action nvarchar(10)
SELECT @action = CASE WHEN COUNT(i.Id) > COUNT(d.Id) THEN 'inserted'
WHEN COUNT(i.Id) < COUNT(d.Id) THEN 'deleted' ELSE 'updated' END
FROM inserted i FULL JOIN deleted d ON i.Id = d.Id
Dans le deuxième scénario, vous n'avez pas besoin d'utiliser la colonne IDENTITTY
CREATE TRIGGER [dbo].[insupddel_yourTable] ON [yourTable]
FOR INSERT, UPDATE, DELETE
AS
IF @@ROWCOUNT = 0 return
SET NOCOUNT ON;
DECLARE @action nvarchar(10),
@insCount int = (SELECT COUNT(*) FROM inserted),
@delCount int = (SELECT COUNT(*) FROM deleted)
SELECT @action = CASE WHEN @insCount > @delCount THEN 'inserted'
WHEN @insCount < @delCount THEN 'deleted' ELSE 'updated' END
Solution rapide MySQL
Au fait: j'utilise MySQL PDO.
(1) Dans une table d'incrémentation automatique, obtenez la valeur la plus élevée (mon nom de colonne = id) de la colonne incrémentée une fois que chaque script est exécuté en premier:
$select = "
SELECT MAX(id) AS maxid
FROM [tablename]
LIMIT 1
";
(2) Exécutez la requête MySQL comme vous le feriez normalement et convertissez le résultat en entier, par exemple:
$iMaxId = (int) $result[0]->maxid;
(3) Après la requête "INSERT INTO ... ON DUPLICATE KEY UPDATE", obtenez le dernier identifiant inséré de la manière que vous préférez, par exemple:
$iLastInsertId = (int) $db->lastInsertId();
(4) Comparez et réagissez: Si le lastInsertId est supérieur au plus haut du tableau, il s'agit probablement d'un INSERT, n'est-ce pas? Et vice versa.
if ($iLastInsertId > $iMaxObjektId) {
// IT'S AN INSERT
}
else {
// IT'S AN UPDATE
}
Je sais que c'est rapide et peut-être sale. Et c'est un ancien post. Mais, hé, je cherchais une solution depuis longtemps, et peut-être que quelqu'un trouve mon chemin quelque peu utile de toute façon. Bonne chance!
DECLARE @INSERTEDCOUNT INT,
@DELETEDCOUNT INT
SELECT @INSERTEDCOUNT = COUNT([YourColumnName]) FROM inserted
SELECT @DELETEDCOUNT = COUNT([YourColumnName]) FROM deleted
SI sa mise à jour
@INSERTEDCOUNT = 1
@DELETEDCOUNT = 1
si son insertion
@INSERTEDCOUNT = 1
@DELETEDCOUNT = 0
J'ai utilisé ces requêtes exists (select * from inserted/deleted)
pendant longtemps, mais ce n'est toujours pas suffisant pour les opérations CRUD vides (lorsqu'il n'y a pas d'enregistrements dans les tables inserted
et deleted
.). Donc, après avoir étudié un peu ce sujet, j'ai trouvé une solution plus précise:
declare
@columns_count int = ?? -- number of columns in the table,
@columns_updated_count int = 0
-- this is kind of long way to get number of actually updated columns
-- from columns_updated() mask, it's better to create helper table
-- or at least function in the real system
with cte_columns as (
select @columns_count as n
union all
select n - 1 from cte_columns where n > 1
), cte_bitmasks as (
select
n,
(n - 1) / 8 + 1 as byte_number,
power(2, (n - 1) % 8) as bit_mask
from cte_columns
)
select
@columns_updated_count = count(*)
from cte_bitmasks as c
where
convert(varbinary(1), substring(@columns_updated_mask, c.byte_number, 1)) & c.bit_mask > 0
-- actual check
if exists (select * from inserted)
if exists (select * from deleted)
select @operation = 'U'
else
select @operation = 'I'
else if exists (select * from deleted)
select @operation = 'D'
else if @columns_updated_count = @columns_count
select @operation = 'I'
else if @columns_updated_count > 0
select @operation = 'U'
else
select @operation = 'D'
Il est également possible d'utiliser columns_updated() & power(2, column_id - 1) > 0
pour voir si la colonne est mise à jour, mais cela n'est pas sûr pour les tables avec un grand nombre de colonnes. J'ai utilisé une méthode de calcul un peu complexe (voir l'article utile ci-dessous).
En outre, cette approche classera toujours de manière incorrecte certaines mises à jour en tant qu'insert (si chaque colonne de la table est affectée par update), et elle classera probablement les inserts dans lesquels seules les valeurs par défaut sont insérées en tant que suppressions, mais celles-ci sont le roi d'opérations rares (à bail dans mon système, ils sont). De plus, je ne sais pas comment améliorer cette solution pour le moment.
declare @result as smallint
declare @delete as smallint = 2
declare @insert as smallint = 4
declare @update as smallint = 6
SELECT @result = POWER(2*(SELECT count(*) from deleted),1) + POWER(2*(SELECT
count(*) from inserted),2)
if (@result & @update = @update)
BEGIN
print 'update'
SET @result=0
END
if (@result & @delete = @delete)
print 'delete'
if (@result & @insert = @insert)
print 'insert'
juste moyen simple
CREATE TRIGGER [dbo].[WO_EXECUTION_TRIU_RECORD] ON [dbo].[WO_EXECUTION]
WITH EXECUTE AS CALLER
FOR INSERT, UPDATE
AS
BEGIN
select @vars = [column] from inserted
IF UPDATE([column]) BEGIN
-- do update action base on @vars
END ELSE BEGIN
-- do insert action base on @vars
END
END