sql >> Databáze >  >> RDS >> Access

Návrh spouštěče Microsoft T-SQL

Návrh spouštěče Microsoft T-SQL

Při vytváření projektu zahrnujícího front-end Access a backend SQL Server jsme narazili na tuto otázku. Měli bychom na něco použít spoušť? Řešením může být návrh spouštěče SQL Server pro aplikaci Access, ale pouze po pečlivém zvážení. Někdy se to navrhuje jako způsob, jak udržet obchodní logiku v databázi, spíše než v aplikaci. Normálně se mi líbí mít obchodní logiku definovanou co nejblíže k databázi. Je tedy trigger řešením, které chceme pro náš Access front-end?

Zjistil jsem, že kódování spouštěče SQL vyžaduje další úvahy, a pokud nebudeme opatrní, můžeme skončit s větším nepořádkem, než jsme začali. Tento článek si klade za cíl pokrýt všechna úskalí a techniky, které můžeme použít, abychom zajistili, že když vytvoříme databázi se spouštěči, budou fungovat v náš prospěch, spíše než jen přidávat složitost kvůli složitosti.

Podívejme se na pravidla…

Pravidlo č. 1:Nepoužívejte spoušť!

Vážně. Pokud sáhnete po spoušti jako první ráno, pak toho budete v noci litovat. Největším problémem spouštěčů obecně je to, že mohou účinně zatemnit vaši obchodní logiku a zasahovat do procesů, které by spouštěč potřebovat neměly. Viděl jsem několik návrhů, jak vypnout spouštěče, když provádíte hromadné načítání nebo něco podobného. Tvrdím, že jde o zápach velkého kódu. Neměli byste používat spoušť, pokud musí být podmíněně zapnuta nebo vypnuta.

Ve výchozím nastavení bychom měli nejprve psát uložené procedury nebo pohledy. U většiny scénářů odvedou svou práci v pohodě. Nepřidávejte sem magii.

Tak proč ten článek o spoušti?

Protože spouštěče mají své využití. Musíme rozpoznat, kdy bychom měli spouštěče použít. Musíme je také napsat tak, aby nám to více pomohlo, než aby nám to ublížilo.

Pravidlo č. 2:Opravdu potřebuji spoušť?

Teoreticky zní triggery pěkně. Poskytují nám model založený na událostech pro správu změn, jakmile dojde k jejich úpravě. Ale pokud vše, co potřebujete, je ověřit některá data nebo zajistit, aby byly naplněny některé skryté sloupce nebo tabulky protokolování…. Myslím, že zjistíte, že uložená procedura dělá práci efektivněji a odstraňuje magický aspekt. Kromě toho lze snadno otestovat zápis uložené procedury; jednoduše nastavte nějaká falešná data a spusťte uloženou proceduru, ověřte, zda jsou výsledky takové, jaké jste očekávali. Doufám, že používáte testovací rámec, jako je tSQLt.

A je důležité si uvědomit, že je obvykle efektivnější použít omezení databáze než spouštěč. Pokud tedy potřebujete pouze ověřit, že hodnota je platná v jiné tabulce, použijte omezení cizího klíče. Ověření, že je hodnota v určitém rozsahu, vyžaduje kontrolní omezení. To by měla být vaše výchozí volba pro tento druh ověření.

Kdy tedy budeme vlastně potřebovat spoušť?

Scvrkává se na případy, kdy opravdu chcete, aby obchodní logika byla ve vrstvě SQL. Možná proto, že máte více klientů v různých programovacích jazycích, kteří vkládají/aktualizují tabulku. Bylo by velmi chaotické duplikovat obchodní logiku napříč každým klientem v jejich příslušném programovacím jazyce, a to také znamená více chyb. Pro scénáře, kde není praktické vytvořit střední vrstvu, jsou spouštěče nejlepším postupem pro prosazování obchodního pravidla, které nelze vyjádřit jako omezení.

Chcete-li použít příklad specifický pro Access. Předpokládejme, že chceme prosadit obchodní logiku při úpravách dat prostřednictvím aplikace. Možná máme více formulářů pro zadávání dat vázaných na jednu stejnou tabulku, nebo možná potřebujeme podporovat složitý formulář pro zadávání dat, kde se na úpravách musí podílet více základních tabulek. Možná, že formulář pro zadávání dat musí podporovat nenormalizované položky, které pak znovu skládáme do normalizovaných dat. Ve všech těchto případech bychom mohli napsat kód VBA, ale to může být obtížné udržovat a ověřovat ve všech případech. Triggers nám pomáhají přesunout logiku z VBA do T-SQL. Obchodní logika zaměřená na data je obecně nejlépe umístěna co nejblíže datům.

Pravidlo č. 3:Spouštěč musí být založen na nastavení, nikoli na základě řádků

Zdaleka nejčastější chybou spouštěče je, že běží na řádcích. Často vidíme kód podobný tomuto:

--Špatný kód! Nepoužívejte!CREATE TRIGGER dbo.SomeTriggerON dbo.SomeTable PO INSERTASZAČNĚTE DECLARE @NewTotal money; DECLARE @NewID int; SELECT TOP 1 @NewID =SalesOrderID, @NewTotal =SalesAmount FROM vložen; AKTUALIZOVAT SADA dbo.SalesOrder OrderTotal =OrderTotal + @NewTotal WHERE SalesOrderID =@SalesOrderIDEND;

Prozradí by měla být pouhá skutečnost, že u stolu byla SELECT TOP 1 vložena. To bude fungovat, pouze pokud vložíme pouze jeden řádek. Ale když je to více než jeden řádek, co se stane s těmi nešťastnými řadami, které přišly na druhé a poté? Můžeme to zlepšit tím, že uděláme něco podobného:

--Stále špatný kód! Nepoužívejte!CREATE TRIGGER dbo.SomeTriggerON dbo.SomeTable PO INSERTASZAČNĚTE SLOUČIT DO dbo.SalesOrder JAKO s POUŽITÍ vloženo JAKO i ON s.SalesOrderID =i.SalesOrderID PŘI SHODĚ POTOM AKTUALIZUJTE SET +Nový OrderTotal; END> 

Toto je nyní založeno na množinách a je tedy mnohem vylepšeno, ale stále má další problémy, které uvidíme v několika dalších pravidlech…

Pravidlo č. 4:Použijte místo toho zobrazení.

Pohled může mít připojenou spoušť. To nám dává tu výhodu, že se vyhneme problémům spojeným se spouštěči tabulky. Byli bychom schopni jednoduše hromadně importovat čistá data do tabulky, aniž bychom museli deaktivovat jakékoli spouštěče. Kromě toho, spouštěč, který je zobrazen, z něj činí výslovnou volbu opt-in. Pokud máte funkce související se zabezpečením nebo obchodní pravidla, která vyžadují spouštění spouštěčů, můžete jednoduše zrušit oprávnění přímo v tabulce a místo toho je nasměrovat do nového zobrazení. To zajistí, že projdete projektem a poznamenáte si, kde je potřeba aktualizovat tabulku, abyste je pak mohli sledovat, zda v nich nejsou nějaké možné chyby nebo problémy.

Nevýhodou je, že k pohledu může být připojen pouze MÍSTO spouštěčů, což znamená, že ekvivalentní úpravy v základní tabulce musíte v rámci spouštěče explicitně provést sami. Mám však tendenci si myslet, že je to takto lepší, protože to také zajišťuje, že přesně víte, jaká modifikace bude, a poskytuje vám tak stejnou úroveň kontroly, jakou obvykle máte v rámci uložené procedury.

Pravidlo č. 5:Spouštěč by měl být hloupě jednoduchý.

Pamatujete si komentář o ladění a testování uložené procedury? Nejlepší laskavost, kterou pro sebe můžeme udělat, je ponechat obchodní logiku v uložené proceduře a nechat ji místo toho vyvolat spoušť. Nikdy byste neměli psát obchodní logiku přímo do spouštěče; což efektivně zalévá databázi. Nyní je zmrazen do tvaru a může být problematické adekvátně otestovat logiku. Váš testovací postroj nyní musí zahrnovat nějakou úpravu základního stolu. To není dobré pro psaní jednoduchých a opakovatelných testů. Toto by mělo být nejsložitější, protože váš spouštěč by měl být povolen:

CREATE TRIGGER [dbo].[SomeTrigger]ON [dbo].[SomeView] MÍSTO INSERT, UPDATE, DELETEASBEGIN DECLARE @SomeIDs AS SomeIDTableType --Proveďte sloučení do základní tabulky MERGE INTO dbo.SomeTable AS t USING insert JAKO i NA t.SomeID =i.SomeID PŘI SHODĚ PAK AKTUALIZOVAT SET t.SomeStuff =i.SomeStuff, t.OtherStuff =i.OtherStuff, KDYŽ SE NEPŘIDÍ, PAK VLOŽTE ( SomeStuff, OtherStuff ) HODNOTY ( i.SomeStuff, i.OtherStuff) OUTPUT vložen.SomeID INTO @SomeIDs(SomeID); DELETE FROM dbo.SomeTable OUTPUT deleted.SomeID INTO @SomeIDs(SomeID) WHERE EXISTS ( SELECT NULL FROM delete AS d WHERE d.SomeID =SomeTable.SomeID ) AND NOT EXISTS (SELECT NULL FROM vložen AS i WHERE SomeTableI.SomeID =SomeID); EXEC dbo.uspUpdateSomeStuff @SomeIDs;END;

První částí spouštěče je v podstatě provést skutečné úpravy na základní tabulce, protože je to MÍSTO spouštěče, takže musíme provést všechny úpravy, které se budou lišit v závislosti na tabulkách, které potřebujeme spravovat. Je třeba zdůraznit, že úpravy by měly být převážně doslovné. Žádná data nepřepočítáváme ani netransformujeme. Všechnu tu práci navíc ušetříme na konci, kde vše, co děláme v rámci spouštěče, je naplnění seznamu záznamů, které byly spouštěčem upraveny, a poskytnutí uložené proceduře pomocí parametru s hodnotou tabulky. Všimněte si, že ani neuvažujeme o tom, jaké záznamy byly změněny, ani jak byly změněny. Vše, co lze provést v rámci uložené procedury.

Pravidlo č. 6:Spouštěč by měl být idempotentní, kdykoli je to možné.

Obecně řečeno, spouštěče MUSÍ být idempotentní. To platí bez ohledu na to, zda se jedná o spouštěč založený na tabulce nebo zobrazení. Týká se to zejména těch, které potřebují upravit data na základních tabulkách, odkud je trigger sledován. Proč? Protože pokud lidé upravují data, která zachytí spouštěč, mohli by si uvědomit, že udělali chybu, upravit je znovu nebo možná jednoduše upravit stejný záznam a uložit jej třikrát. Nebudou rádi, když zjistí, že se sestavy mění pokaždé, když provedou úpravu, která nemá upravovat výstup sestavy.

Abychom byli jasnější, mohlo by být lákavé zkusit optimalizovat spouštěč tím, že uděláte něco podobného:

S SourceData AS ( SELECT OrderID, SUM(SalesAmount) AS NewSaleTotal Z vloženo SKUPINY PODLE ID objednávky) SLUČIT DO dbo.SalesOrder JAKO POUŽÍVÁNÍ Zdrojových dat JAKO DON o.ID objednávky =d.ID objednávky PŘI PŘIŘAZENÍ POTOM AKTUALIZOVAT SOUČET o.OrderTotal + d.NewSaleTotal;

Vyhneme se přepočítávání nového součtu pouhým přezkoumáním upravených řádků ve vložené tabulce, že? Ale když uživatel upraví záznam tak, aby opravil překlep ve jménu zákazníka, co se stane? Skončíme s falešným součtem a spoušť nyní pracuje proti nám.

Nyní byste měli vidět, proč nám pravidlo #4 pomáhá tím, že vytlačí pouze primární klíče do uložené procedury, místo abychom se pokoušeli předat jakákoli data do uložené procedury nebo to dělali přímo uvnitř spouštěče, jak by to udělal vzorek .

Místo toho chceme mít nějaký kód podobný tomuto v rámci uložené procedury:

VYTVOŘENÍ POSTUPU dbo.uspUpdateSalesTotal ( @SalesOrders SalesOrderTableType POUZE PRO ČTENÍ) ASZAČNĚTE S SourceData AS ( SELECT s.OrderID, SUM(s.SalesAmount) AS NewSaleTotal FROM dbo.SalesOrder EXISTSRE SELECTS FREHEDER .SalesOrderID =s.SalesOrderID ) GROUP BY OrderID ) SLOUČIT DO dbo.SalesOrder JAKO o POUŽITÍM SourceData AS d ON o.OrderID =d.OrderID PŘI SHODU A POTOM AKTUALIZOVAT SADA o.OrderTotal =Celkem d.>ENDPreale 

Pomocí @SalesOrders můžeme stále selektivně aktualizovat pouze řádky, které byly ovlivněny spouštěčem, a také můžeme úplně přepočítat nový součet a udělat z něj nový součet. Takže i když uživatel udělal překlep ve jménu zákazníka a upravil jej, každé uložení přinese pro daný řádek stejný výsledek.

Ještě důležitější je, že tento přístup nám také poskytuje snadný způsob, jak opravit součty. Předpokládejme, že musíme provést hromadný import a import neobsahuje součet, takže jej musíme vypočítat sami. Uloženou proceduru můžeme zapsat přímo do tabulky. Poté můžeme vyvolat výše uvedenou uloženou proceduru předáním ID z importu a jsme v pořádku. Logika, kterou používáme, tedy není svázána spouštěčem za pohledem. To pomáhá, když je logika pro hromadný import, který provádíme, zbytečná.

Pokud zjistíte, že máte problém s tím, aby byl váš spouštěč idempotentní, je to silný náznak toho, že možná budete muset místo toho použít uloženou proceduru a volat ji přímo z aplikace, místo abyste se spoléhali na spouštěče. Jednou z výrazných výjimek z tohoto pravidla je situace, kdy je spouštěč primárně určen jako spouštěč auditu. V tomto případě chcete do tabulky auditu zapsat nový řádek pro každou úpravu, včetně všech překlepů, které uživatel udělá. To je v pořádku, protože v takovém případě nedochází k žádným změnám dat, se kterými uživatel komunikuje. Z POV uživatele je to stále stejný výsledek. Ale kdykoli potřebuje spouštěč manipulovat se stejnými daty, se kterými uživatel pracuje, je mnohem lepší, když je idempotentní.

Dokončuji

Doufejme, že nyní můžete vidět, jak mnohem obtížnější může být navrhnout dobře fungující spoušť. Z toho důvodu byste měli pečlivě zvážit, zda se tomu můžete úplně vyhnout a použít přímá volání s uloženou procedurou. Pokud jste však došli k závěru, že ke správě úprav provedených prostřednictvím zobrazení musíte mít spouštěče, doufám, že vám pravidla pomohou. Vytvoření základní sady spouště je dostatečně snadné s několika úpravami. Aby to idempotentní, obvykle vyžaduje více přemýšlet o tom, jak budete implementovat své uložené procedury.

Máte-li nějaké další návrhy nebo pravidla, o které se chcete podělit, pište do komentářů!


  1. Jak přidat nový sloupec do tabulky MYSQL?

  2. 7 způsobů, jak vrátit všechny tabulky s cizími klíči na SQL Server

  3. EF a TransactionScope pro SQL Server i Oracle bez eskalace/překlenutí na DTC?

  4. Jaký je rozdíl mezi VARCHAR a CHAR?