sql >> Database >  >> RDS >> Access

Een Microsoft T-SQL-trigger ontwerpen

Een Microsoft T-SQL-trigger ontwerpen

Bij het bouwen van een project met een Access-front-end en een SQL Server-backend, zijn we deze vraag tegengekomen. Moeten we ergens een trigger voor gebruiken? Het ontwerpen van een SQL Server-trigger voor Access-toepassing kan een oplossing zijn, maar alleen na zorgvuldige overwegingen. Soms wordt dit voorgesteld als een manier om de bedrijfslogica in de database te houden, in plaats van in de toepassing. Normaal gesproken vind ik het prettig om de bedrijfslogica zo dicht mogelijk bij de database te hebben gedefinieerd. Dus, is trigger de oplossing die we willen voor onze Access front-end?

Ik heb ontdekt dat het coderen van een SQL-trigger extra overwegingen vereist en als we niet oppassen, kunnen we eindigen met een grotere puinhoop dan we begonnen. Het artikel is bedoeld om alle valkuilen en technieken te behandelen die we kunnen gebruiken om ervoor te zorgen dat wanneer we een database met triggers bouwen, ze in ons voordeel werken, in plaats van alleen maar complexiteit toe te voegen omwille van de complexiteit.

Laten we eens kijken naar de regels...

Regel #1:Gebruik geen trigger!

Ernstig. Als je 's ochtends als eerste naar de trigger grijpt, zul je er' s nachts spijt van krijgen. Het grootste probleem met triggers in het algemeen is dat ze uw bedrijfslogica effectief kunnen verdoezelen en processen kunnen verstoren die geen trigger nodig hebben. Ik heb enkele suggesties gezien om triggers uit te schakelen wanneer u een bulklading of iets dergelijks doet. Ik beweer dat dit een grote codegeur is. U mag geen trigger gebruiken als deze voorwaardelijk moet worden in- of uitgeschakeld.

Standaard moeten we eerst opgeslagen procedures of views schrijven. Voor de meeste scenario's zullen ze het werk prima doen. Laten we hier geen magie toevoegen.

Dus waarom dan het artikel over trigger?

Omdat triggers hun nut hebben. We moeten herkennen wanneer we triggers moeten gebruiken. We moeten ze ook zo schrijven dat het ons meer helpt dan dat het ons pijn doet.

Regel #2:Heb ik echt een trigger nodig?

In theorie klinken triggers leuk. Ze bieden ons een op gebeurtenissen gebaseerd model om wijzigingen te beheren zodra ze worden gewijzigd. Maar als u alleen wat gegevens hoeft te valideren of ervoor te zorgen dat enkele verborgen kolommen of logtabellen worden gevuld.... Ik denk dat je zult merken dat een opgeslagen procedure het werk efficiënter doet en het magische aspect verwijdert. Verder is het schrijven van een opgeslagen procedure eenvoudig te testen; stel eenvoudig wat nepgegevens in en voer de opgeslagen procedure uit, controleer of de resultaten zijn wat u verwachtte. Ik hoop dat je een testframework zoals tSQLt gebruikt.

En het is belangrijk op te merken dat het meestal efficiënter is om databasebeperkingen te gebruiken dan een trigger. Dus als u alleen wilt valideren dat een waarde geldig is in een andere tabel, gebruik dan een externe sleutelbeperking. Om te valideren dat een waarde binnen een bepaald bereik valt, is een controlebeperking nodig. Dat zou je standaardkeuze moeten zijn voor dat soort validaties.

Dus wanneer hebben we eigenlijk een trigger nodig?

Het komt neer op gevallen waarin u echt wilt dat de bedrijfslogica zich in de SQL-laag bevindt. Misschien omdat je meerdere clients in verschillende programmeertalen hebt die invoegingen/updates aan een tabel doen. Het zou erg rommelig zijn om de bedrijfslogica voor elke client in hun respectievelijke programmeertaal te dupliceren en dit betekent ook meer bugs. Voor scenario's waarin het niet praktisch is om een ​​middelste laag te maken, is triggers de beste manier om de bedrijfsregel af te dwingen die niet als een beperking kan worden uitgedrukt.

Om een ​​voorbeeld te gebruiken dat specifiek is voor Access. Stel dat we bedrijfslogica willen afdwingen bij het wijzigen van gegevens via de applicatie. Misschien hebben we meerdere formulieren voor gegevensinvoer die aan dezelfde tabel zijn gebonden, of misschien moeten we complexe formulieren voor gegevensinvoer ondersteunen waarbij meerdere basistabellen moeten deelnemen aan de bewerking. Misschien moet het gegevensinvoerformulier niet-genormaliseerde invoer ondersteunen, die we vervolgens opnieuw samenstellen in genormaliseerde gegevens. In al die gevallen zouden we gewoon VBA-code kunnen schrijven, maar dat kan voor alle gevallen moeilijk te onderhouden en te valideren zijn. Triggers helpt ons om de logica van VBA naar T-SQL te verplaatsen. De datacentrische bedrijfslogica wordt over het algemeen het best zo dicht mogelijk bij de gegevens geplaatst.

Regel #3:Trigger moet set-based zijn, niet row-based

Veruit de meest voorkomende fout die met een trigger wordt gemaakt, is om deze op rijen te laten draaien. Vaak zien we code die lijkt op deze:

--Slechte code! Niet gebruiken!CREATE TRIGGER dbo.SomeTriggerON dbo.SomeTable NA INSERTASBEGIN VERKLAREN @NewTotal geld; VERKLAREN @NewID int; SELECTEER TOP 1 @NewID =SalesOrderID, @NewTotal =SalesAmount FROM ingevoegd; UPDATE dbo.SalesOrder SET OrderTotal =OrderTotal + @NewTotal WHERE SalesOrderID =@SalesOrderIDEND;

De weggever zou het simpele feit moeten zijn dat er een SELECT TOP 1 was van een tafel ingevoegd. Dit werkt alleen zolang we slechts één rij invoegen. Maar als het meer dan één rij is, wat gebeurt er dan met die ongelukkige rijen die 2e en daarna kwamen? We kunnen dat verbeteren door iets soortgelijks als dit te doen:

--Nog steeds een slechte code! Niet gebruiken!CREATE TRIGGER dbo.SomeTriggerON dbo.SomeTable NA INSERTASBEGIN SAMENVOEGEN IN dbo.SalesOrder AS s USING insert AS i ON s.SalesOrderID =i.SalesOrderID WHEN MATCHED THEN UPDATE SET OrderTotal =OrderTotal;END;> 

Dit is nu set-gebaseerd en dus veel verbeterd, maar dit heeft nog andere problemen die we in de volgende paar regels zullen zien...

Regel #4:Gebruik in plaats daarvan een weergave.

Aan een weergave kan een trigger zijn gekoppeld. Dit geeft ons het voordeel dat we problemen vermijden die verband houden met het activeren van een tabel. We zouden eenvoudig schone gegevens in bulk in de tabel kunnen importeren zonder triggers uit te schakelen. Bovendien maakt een 'trigger on view' het een expliciete opt-in keuze. Als u beveiligingsgerelateerde functionaliteiten of bedrijfsregels heeft die het uitvoeren van triggers vereisen, kunt u eenvoudig de machtigingen op de tabel rechtstreeks intrekken en ze in plaats daarvan naar de nieuwe weergave leiden. Dat zorgt ervoor dat u het project doorloopt en opmerkt waar updates voor de tabel nodig zijn, zodat u ze vervolgens kunt volgen op mogelijke bugs of problemen.

Het nadeel is dat aan een view alleen IN PLAATS VAN triggers kan worden gekoppeld, wat betekent dat u de equivalente wijzigingen op de basistabel zelf expliciet binnen de trigger moet uitvoeren. Ik denk echter dat het op die manier beter is, omdat het er ook voor zorgt dat je precies weet wat de wijziging zal zijn, en je dus hetzelfde niveau van controle geeft dat je normaal hebt binnen een opgeslagen procedure.

Regel #5:De trigger moet stom simpel zijn.

Herinner je je de opmerking over het debuggen en testen van een opgeslagen procedure? De beste gunst die we onszelf kunnen doen, is om de bedrijfslogica in een opgeslagen procedure te houden en de trigger deze in plaats daarvan te laten aanroepen. U moet nooit bedrijfslogica rechtstreeks in de trigger schrijven; dat is effectief beton in de database gieten. Het is nu bevroren in de vorm en het kan problematisch zijn om de logica adequaat te testen. Uw testharnas moet nu een wijziging aan de basistabel met zich meebrengen. Dit is niet goed voor het schrijven van eenvoudige en herhaalbare tests. Dit zou het meest gecompliceerd moeten zijn, aangezien uw trigger zou moeten zijn:

CREATE TRIGGER [dbo].[SomeTrigger]ON [dbo].[SomeView] IN PLAATS VAN INSERT, UPDATE, DELETEASBEGIN DECLARE @SomeIDs AS SomeIDTableType --Voer de samenvoeging uit in de basistabel MERGE INTO dbo.SomeTable AS t USING ingevoegd AS i ON t.SomeID =i.SomeID WANNEER OVEREENGEKOMEN DAN UPDATE SET t.SomeStuff =i.SomeStuff, t.OtherStuff =i.OtherStuff WANNEER NIET OVEREENGEKOMEN, VOEG DAN (SomeStuff, OtherStuff) WAARDEN IN (i.SomeStuff, i.OtherStuff) OUTPUT ingevoegd.SomeID INTO @SomeIDs(SomeID); VERWIJDEREN VAN dbo.SomeTable OUTPUT verwijderd.SomeID INTO @SomeIDs(SomeID) WHERE EXISTS (SELECT NULL FROM verwijderd AS d WHERE d.SomeID =SomeTable.SomeID) EN NIET BESTAAT (SELECTEER NULL UIT ingevoegd AS i WHERE i.SomeID =SomeTable. een ID); EXEC dbo.uspUpdateSomeStuff @SomeIDs;END;

Het eerste deel van de trigger is om in feite de daadwerkelijke wijzigingen aan de basistabel uit te voeren, omdat het een IN PLAATS VAN een trigger is, dus we moeten alle wijzigingen uitvoeren die verschillen afhankelijk van de tabellen die we moeten beheren. Het is de moeite waard om te benadrukken dat wijzigingen voornamelijk woordelijk moeten zijn. We herberekenen of transformeren de gegevens niet. We bewaren al dat extra werk aan het einde, waar we binnen de trigger alleen maar een lijst met records vullen die door de trigger zijn gewijzigd en een opgeslagen procedure verstrekken met behulp van een tabelwaardeparameter. Merk op dat we niet eens overwegen welke records zijn gewijzigd of hoe deze zijn gewijzigd. Dat kan allemaal binnen de opgeslagen procedure.

Regel #6:de trigger moet waar mogelijk idempotent zijn.

Over het algemeen moeten de triggers MOET idempotent zijn. Dit is van toepassing ongeacht of het een op tabellen of weergave gebaseerde trigger is. Het is vooral van toepassing op degenen die de gegevens in de basistabellen moeten wijzigen van waaruit de trigger bewaakt. Waarom? Omdat als mensen de gegevens wijzigen die door de trigger worden opgepikt, ze zich misschien realiseren dat ze een fout hebben gemaakt, het opnieuw hebben bewerkt of misschien gewoon hetzelfde record bewerken en het 3 keer opslaan. Ze zullen niet blij zijn als ze ontdekken dat de rapporten elke keer veranderen als ze een bewerking uitvoeren die de uitvoer van het rapport niet zou moeten wijzigen.

Om explicieter te zijn, het kan verleidelijk zijn om te proberen de trigger te optimaliseren door iets soortgelijks als dit te doen:

MET SourceData AS (SELECT OrderID, SUM(SalesAmount) AS NewSaleTotal FROM ingevoegd GROUP BY OrderID)SEMGE INTO dbo.SalesOrder ALS oUSING SourceData AS dON o.OrderID =d.OrderIDWHEN MATCHED DAN UPDATE SET =o.OrderTotal + d.NewSaleTotal;

We kunnen voorkomen dat we het nieuwe totaal opnieuw berekenen door alleen de gewijzigde rijen in de ingevoegde tabel te bekijken, toch? Maar wat gebeurt er als de gebruiker het record bewerkt om een ​​typefout in de naam van de klant te corrigeren? We eindigen met een neptotaal en de trigger werkt nu tegen ons.

Inmiddels zou je moeten begrijpen waarom regel #4 ons helpt door alleen de primaire sleutels naar de opgeslagen procedure uit te drukken, in plaats van te proberen gegevens door te geven aan de opgeslagen procedure of het direct in de trigger te doen, zoals het voorbeeld zou hebben gedaan .

In plaats daarvan willen we een soortgelijke code hebben binnen een opgeslagen procedure:

PROCEDURE MAKEN dbo.uspUpdateSalesTotal ( @SalesOrders SalesOrderTableType ALLEEN LEZEN) ASBEGIN MET SourceData AS ( SELECT s.OrderID, SUM(s.SalesAmount) AS NewSaleTotal FROM dbo.SalesOrder AS s WHEREULLISTROM xWHEREULLISTROM @ SELECT Sa .SalesOrderID =s.SalesOrderID ) GROEPEREN OP OrderID ) SAMENVOEGEN IN dbo.SalesOrder AS o GEBRUIK SourceData AS d ON o.OrderID =d.OrderID WHEN MATCHED DAN UPDATE SET o.OrderTotal =d.NewSale>Total; 

Met behulp van @SalesOrders kunnen we nog steeds selectief alleen de rijen bijwerken die door de trigger zijn beïnvloed, en we kunnen ook het nieuwe totaal opnieuw berekenen en er het nieuwe totaal van maken. Dus zelfs als de gebruiker een typefout heeft gemaakt in de naam van de klant en deze heeft bewerkt, levert elke opslag hetzelfde resultaat op voor die rij.

Belangrijker is dat deze aanpak ons ​​ook een gemakkelijke manier biedt om de totalen vast te stellen. Stel dat we bulkimport moeten doen, en de import bevat niet het totaal, dus we moeten het zelf berekenen. We kunnen de opgeslagen procedure schrijven om rechtstreeks naar de tabel te schrijven. We kunnen dan de bovenstaande opgeslagen procedure aanroepen door de ID's van de import door te geven, en we zijn allemaal goed. De logica die we gebruiken, is dus niet gebonden aan de trigger achter de weergave. Dat helpt wanneer de logica niet nodig is voor de bulkimport die we uitvoeren.

Als u problemen ondervindt bij het idempotent maken van uw trigger, is dit een sterke aanwijzing dat u in plaats daarvan een opgeslagen procedure moet gebruiken en deze rechtstreeks vanuit uw toepassing moet aanroepen in plaats van te vertrouwen op triggers. Een opmerkelijke uitzondering op deze regel is wanneer de trigger in de eerste plaats bedoeld is als controletrigger. In dit geval wilt u voor elke bewerking een nieuwe rij naar de controletabel schrijven, inclusief alle typefouten die de gebruiker maakt. Dit is OK, want in dat geval zijn er geen wijzigingen in de gegevens waarmee de gebruiker interactie heeft. Vanuit de POV van de gebruiker is het nog steeds hetzelfde resultaat. Maar wanneer de trigger dezelfde gegevens moet manipuleren waarmee de gebruiker werkt, is het veel beter wanneer deze idempotent is.

Afronden

Hopelijk kun je nu zien hoeveel moeilijker het kan zijn om een ​​goed opgevoede trigger te ontwerpen. Om die reden moet u zorgvuldig overwegen of u het helemaal kunt vermijden en directe aanroepen kunt gebruiken met de opgeslagen procedure. Maar als u tot de conclusie bent gekomen dat u triggers moet hebben om de wijzigingen die via weergaven zijn aangebracht te beheren, hoop ik dat de regels u zullen helpen. Het maken van de trigger-set-based is eenvoudig genoeg met enkele aanpassingen. Om het idempotent te maken, moet u meestal meer nadenken over hoe u uw opgeslagen procedures gaat implementeren.

Als je nog meer suggesties of regels hebt om te delen, schiet dan maar in de reacties!


  1. Is een RID Lookup sneller dan een Key Lookup?

  2. Hoe ongewenste voorlooptekens uit een string in MySQL te verwijderen

  3. Missiekritieke toegangstoepassingen identificeren en beheren tijdens een ontwikkelingsproject

  4. Een databaseontwerper worden