Projektowanie wyzwalacza Microsoft T-SQL
Czasami podczas tworzenia projektu obejmującego front-end Access i backend SQL Server natknęliśmy się na to pytanie. Czy powinniśmy do czegoś użyć wyzwalacza? Zaprojektowanie wyzwalacza SQL Server dla aplikacji Access może być rozwiązaniem, ale tylko po dokładnym rozważeniu. Czasami jest to sugerowane jako sposób na utrzymanie logiki biznesowej w bazie danych, a nie w aplikacji. Zwykle lubię mieć logikę biznesową zdefiniowaną jak najbliżej bazy danych. Czy zatem wyzwalacz jest rozwiązaniem, którego oczekujemy dla naszego interfejsu Access?
Odkryłem, że kodowanie wyzwalacza SQL wymaga dodatkowych rozważań i jeśli nie będziemy ostrożni, możemy skończyć z większym bałaganem niż na początku. Artykuł ma na celu omówienie wszystkich pułapek i technik, których możemy użyć, aby upewnić się, że kiedy budujemy bazę danych z wyzwalaczami, będą one działać na naszą korzyść, a nie tylko dodawać złożoność ze względu na złożoność.
Rozważmy zasady…
Zasada nr 1:nie używaj wyzwalacza!
Poważnie. Jeśli rano sięgasz po wyzwalacz, wieczorem będziesz tego żałować. Największym problemem związanym z wyzwalaczami jest to, że mogą one skutecznie zaciemniać logikę biznesową i zakłócać procesy, które nie powinny wymagać wyzwalacza. Widziałem kilka sugestii, aby wyłączyć wyzwalacze, gdy wykonujesz ładowanie zbiorcze lub coś podobnego. Twierdzę, że jest to silny zapach kodu. Nie powinieneś używać wyzwalacza, jeśli ma być on warunkowo włączony lub wyłączony.
Domyślnie powinniśmy najpierw pisać procedury składowane lub widoki. W większości scenariuszy sprawdzą się dobrze. Nie dodawajmy tu magii.
Dlaczego więc artykuł o wyzwalaczu?
Ponieważ wyzwalacze mają swoje zastosowania. Musimy rozpoznać, kiedy powinniśmy używać wyzwalaczy. Musimy je również napisać w sposób, który pomoże nam bardziej niż zranić nas.
Zasada nr 2:czy naprawdę potrzebuję wyzwalacza?
W teorii wyzwalacze brzmią ładnie. Dostarczają nam model oparty na zdarzeniach do zarządzania zmianami, gdy tylko zostaną zmodyfikowane. Ale jeśli wszystko, czego potrzebujesz, to sprawdzić poprawność niektórych danych lub upewnić się, że niektóre ukryte kolumny lub tabele rejestrowania są wypełnione…. Myślę, że przekonasz się, że procedura składowana wykonuje pracę wydajniej i usuwa aspekt magiczny. Co więcej, pisanie procedury składowanej jest łatwe do przetestowania; po prostu skonfiguruj próbne dane i uruchom procedurę składowaną, sprawdź, czy wyniki są zgodne z oczekiwaniami. Mam nadzieję, że używasz frameworka testowego, takiego jak tSQLt.
Należy zauważyć, że zwykle bardziej wydajne jest użycie ograniczeń bazy danych niż wyzwalacza. Więc jeśli potrzebujesz tylko sprawdzić, czy wartość jest prawidłowa w innej tabeli, użyj ograniczenia klucza obcego. Sprawdzanie, czy wartość mieści się w określonym zakresie, wywołuje ograniczenie sprawdzające. Powinny one być twoim domyślnym wyborem dla tego rodzaju walidacji.
Kiedy więc będziemy potrzebować wyzwalacza?
Sprowadza się to do przypadków, w których naprawdę chcesz, aby logika biznesowa znajdowała się w warstwie SQL. Może dlatego, że masz wielu klientów w różnych językach programowania, którzy wstawiają/aktualizują tabelę. Duplikowanie logiki biznesowej dla każdego klienta w odpowiednim języku programowania byłoby bardzo kłopotliwe, a to oznaczałoby również więcej błędów. W scenariuszach, w których tworzenie warstwy środkowej nie jest praktyczne, wyzwalacze to najlepszy sposób na wymuszanie reguły biznesowej, której nie można wyrazić jako ograniczenia.
Aby użyć przykładu specyficznego dla programu Access. Załóżmy, że podczas modyfikowania danych za pośrednictwem aplikacji chcemy wymusić logikę biznesową. Może mamy wiele formularzy wprowadzania danych powiązanych z tą samą tabelą, a może musimy obsługiwać złożony formularz wprowadzania danych, w którym w edycji musi uczestniczyć wiele tabel podstawowych. Być może formularz wprowadzania danych musi obsługiwać nieznormalizowane wpisy, które następnie ponownie komponujemy w znormalizowane dane. We wszystkich tych przypadkach moglibyśmy po prostu napisać kod VBA, ale może to być trudne do utrzymania i sprawdzenia we wszystkich przypadkach. Wyzwalacze pomagają nam przenieść logikę z VBA do T-SQL. Logika biznesowa zorientowana na dane jest zazwyczaj najlepiej umieszczona jak najbliżej danych.
Zasada nr 3:wyzwalacz musi być oparty na zestawie, a nie wierszu
Zdecydowanie najczęstszym błędem popełnianym przy użyciu wyzwalacza jest uruchamianie go w wierszach. Często widzimy kod podobny do tego:
--Zły kod! Nie używaj!CREATE TRIGGER dbo.SomeTriggerON dbo.SomeTable PO INSERTASBEGIN DECLARE @NewTotal money; ZADEKLARUJ @NewID int; SELECT TOP 1 @NewID =SalesOrderID, @NewTotal =SalesAmount FROM wstawione; UPDATE dbo.SalesOrder SET OrderTotal =OrderTotal + @NewTotal GDZIE SalesOrderID =@SalesOrderIDEND;
Nagrodą powinien być sam fakt, że przy stole znalazło się WYBRANE TOP 1 włożona. To zadziała tylko tak długo, jak wstawimy tylko jeden wiersz. Ale kiedy jest więcej niż jeden rzęd, to co dzieje się z tymi pechowymi rzędami, które znalazły się na drugim miejscu i później? Możemy to poprawić, robiąc coś podobnego do tego:
--Nadal zły kod! Nie używaj! CREATE TRIGGER dbo.SomeTriggerON dbo.SomeTable PO INSERTASBEGIN MERGE INTO dbo.SalesOrder AS s UŻYWANIE wstawiony AS i ON s.SalesOrderID =i.SalesOrderID WHEN MATCHEN THEN UPDATE SET OrderTotal =OrderTotal END + @New;>Jest to teraz oparte na zestawach, a zatem znacznie ulepszone, ale nadal ma inne problemy, które zobaczymy w kilku następnych zasadach…
Zasada 4:zamiast tego użyj widoku.
Widok może mieć dołączony wyzwalacz. Daje nam to tę zaletę, że unikamy problemów związanych z wyzwalaczami tabeli. Moglibyśmy łatwo zbiorczo importować czyste dane do tabeli bez konieczności wyłączania jakichkolwiek wyzwalaczy. Co więcej, widoczny wyzwalacz sprawia, że jest to wyraźny wybór opt-in. Jeśli masz funkcje związane z bezpieczeństwem lub reguły biznesowe, które wymagają uruchomienia wyzwalaczy, możesz po prostu odwołać uprawnienia bezpośrednio w tabeli, a tym samym skierować je do nowego widoku. To gwarantuje, że przejdziesz przez projekt i zauważysz, gdzie potrzebne są aktualizacje tabeli, abyś mógł je śledzić pod kątem wszelkich możliwych błędów lub problemów.
Minusem jest to, że widok może mieć dołączone tylko wyzwalacze INSTEAD OF, co oznacza, że musisz samodzielnie wykonać równoważne modyfikacje w tabeli podstawowej w wyzwalaczu. Jednak wydaje mi się, że tak jest lepiej, ponieważ zapewnia to również, że wiesz dokładnie, jaka będzie modyfikacja, a tym samym zapewnia taki sam poziom kontroli, jaki normalnie masz w ramach procedury składowanej.
Zasada nr 5:wyzwalacz powinien być głupio prosty.
Pamiętasz komentarz dotyczący debugowania i testowania procedury składowanej? Najlepszą przysługą, jaką możemy sobie zrobić, jest zachowanie logiki biznesowej w procedurze składowanej i wywołanie jej przez wyzwalacz. Nigdy nie należy pisać logiki biznesowej bezpośrednio w wyzwalaczu; to skutecznie wylewa beton na bazę danych. Jest teraz zamrożony do kształtu i może być problematyczne, aby odpowiednio przetestować logikę. Twoja wiązka testowa musi teraz obejmować pewne modyfikacje tabeli podstawowej. Nie jest to dobre do pisania prostych i powtarzalnych testów. To powinno być najbardziej skomplikowane, ponieważ twój wyzwalacz powinien być dozwolony:
CREATE TRIGGER [dbo].[SomeTrigger]ON [dbo].[SomeView] ZAMIAST INSERT, UPDATE, DELETEASBEGIN DECLARE @ SomeIDs AS SomeIDTableType --Wykonaj scalanie z tabelą podstawową MERGE INTO dbo. AS i ON t.SomeID =i.SomeID KIEDY DOPASOWANE, A NASTĘPNIE USTAWIĆ AKTUALIZACJĘ t.SomeStuff =i.SomeStuff, t.OtherStuff =i.OtherStuff JEŚLI NIE DOPASOWANO, A NASTĘPNIE WSTAWIĆ ( SomeStuff, OtherStuff ) WARTOŚCI ( i.SomeStuff, i.OtherStuff Wstawiono WYJŚCIE.SomeID INTO @SomeIDs(SomeID); DELETE FROM dbo.SomeTable OUTPUT został usunięty.SomeID INTO @SomeIDs(SomeID) WHERE EXISTS ( SELECT NULL FROM usunięty AS d WHERE d.SomeID =SomeTable.SomeID ) AND NOT EXISTS ( SELECT NULL FROM wstawiony AS i WHERE i. SomeID =SomeTable JakiśIdentyfikator ); EXEC dbo.uspUpdateSomeStuff @SomeIDs;END;Pierwszą częścią wyzwalacza jest wykonanie rzeczywistych modyfikacji w tabeli bazowej, ponieważ jest to wyzwalacz ZAMIAST, więc musimy wykonać wszystkie modyfikacje, które będą się różnić w zależności od tabel, którymi musimy zarządzać. Warto podkreślić, że modyfikacje powinny być głównie dosłowne. Nie przeliczamy ani nie przekształcamy żadnych danych. Oszczędzamy całą tę dodatkową pracę na końcu, gdzie wszystko, co robimy w wyzwalaczu, to wypełnianie listy rekordów, które zostały zmodyfikowane przez wyzwalacz i dostarczanie do procedury składowanej przy użyciu parametru wycenianego w tabeli. Zauważ, że nawet nie zastanawiamy się, jakie rekordy zostały zmienione ani w jaki sposób. Wszystko, co można zrobić w ramach procedury składowanej.
Zasada #6:wyzwalacz powinien być idempotentny, gdy tylko jest to możliwe.
Ogólnie rzecz biorąc, wyzwalacze MUSZĄ być idempotentnym. Ma to zastosowanie niezależnie od tego, czy jest to wyzwalacz oparty na tabeli, czy na widoku. Dotyczy to zwłaszcza tych, które muszą zmodyfikować dane w tabelach bazowych, z których wyzwalacz monitoruje. Czemu? Ponieważ jeśli ludzie modyfikują dane, które zostaną przechwycone przez wyzwalacz, mogą zdać sobie sprawę, że popełnili błąd, edytowali go ponownie lub po prostu edytowali ten sam rekord i zapisali go 3 razy. Nie będą zadowoleni, jeśli odkryją, że raporty zmieniają się za każdym razem, gdy wprowadzają edycję, która nie ma na celu modyfikowania wyników raportu.
Mówiąc dokładniej, może być kuszące zoptymalizowanie wyzwalacza, wykonując coś podobnego do tego:
with SourceData AS ( SELECT OrderID, SUM(SalesAmount) AS NewSaleTotal FROM wstawione GROUP BY OrderID)MERGE INTO dbo.SalesOrder AS oUSING SourceData AS dON o.OrderID =d.OrderIDWHEN MATCHEN THE UPDATE SET o.OrderTotal =o.OrderTotal + d.NewSaleTotal;Możemy uniknąć ponownego obliczania nowej sumy, po prostu przeglądając zmodyfikowane wiersze we wstawionej tabeli, prawda? Ale kiedy użytkownik edytuje rekord, aby poprawić literówkę w nazwie klienta, co się stanie? W efekcie otrzymujemy fałszywą sumę, a spust działa teraz przeciwko nam.
Do tej pory powinieneś zobaczyć, dlaczego reguła nr 4 pomaga nam, wypychając tylko klucze główne do procedury składowanej, zamiast próbować przekazać jakiekolwiek dane do procedury składowanej lub robić to bezpośrednio w wyzwalaczu, jak zrobiłaby próbka .
Zamiast tego chcemy mieć kod podobny do tego w procedurze składowanej:
CREATE PROCEDURE dbo.uspUpdateSalesTotal ( @SalesOrders SalesOrderTableType READONLY) ASBEGIN with SourceData AS ( SELECT s.OrderID, SUM(s.SalesAmount) AS NewSaleTotal FROM dbo.SalesOrder AS s WHERE EXISTS ( x SELECT NULL F .SalesOrderID =s.SalesOrderID ) GRUPUJ WEDŁUG IDZamówienia ) MERGE INTO dbo.SalesOrder AS o UŻYWAJĄC SourceData AS d ON o.OrderID =d.OrderID PO DOPASOWANIU THEN UPDATE SET o.OrderTotal =d.NewSaleTotal;END;Korzystając z @SalesOrders, nadal możemy selektywnie aktualizować tylko te wiersze, na które miał wpływ wyzwalacz, a także możemy całkowicie ponownie obliczyć nową sumę i ustawić ją jako nową sumę. Więc nawet jeśli użytkownik popełnił literówkę w nazwie klienta i ją zmodyfikował, każde zapisanie da ten sam wynik dla tego wiersza.
Co ważniejsze, takie podejście zapewnia nam również łatwy sposób na ustalenie sum. Załóżmy, że musimy wykonać import zbiorczy, a import nie zawiera sumy, więc musimy to obliczyć sami. Możemy napisać procedurę składowaną do bezpośredniego zapisu w tabeli. Następnie możemy wywołać powyższą procedurę składowaną, przekazując identyfikatory z importu i wszystko jest w porządku. W ten sposób logika, której używamy, nie jest związana z wyzwalaczem stojącym za widokiem. Pomaga to, gdy logika jest niepotrzebna dla wykonywanego przez nas importu zbiorczego.
Jeśli masz problem z wykonaniem idempotentnego wyzwalacza, oznacza to, że może być konieczne użycie procedury składowanej i wywołanie jej bezpośrednio z aplikacji zamiast polegania na wyzwalaczach. Jednym godnym uwagi wyjątkiem od tej reguły jest sytuacja, w której wyzwalacz ma być przede wszystkim wyzwalaczem inspekcji. W takim przypadku chcesz zapisać nowy wiersz w tabeli kontroli dla każdej edycji, w tym wszystkich literówek, które wprowadził użytkownik. Jest to w porządku, ponieważ w takim przypadku nie ma żadnych zmian w danych, z którymi użytkownik wchodzi w interakcję. Z punktu widzenia użytkownika to wciąż ten sam wynik. Ale za każdym razem, gdy wyzwalacz musi manipulować tymi samymi danymi, z którymi pracuje użytkownik, znacznie lepiej jest, gdy jest idempotentny.
Zawijanie
Mam nadzieję, że do tej pory widzisz, o ile trudniejsze może być zaprojektowanie dobrze zachowującego się wyzwalacza. Z tego powodu należy dokładnie rozważyć, czy można całkowicie tego uniknąć i używać bezpośrednich wywołań z procedurą składowaną. Ale jeśli doszedłeś do wniosku, że musisz mieć wyzwalacze, aby zarządzać modyfikacjami wprowadzanymi za pomocą widoków, mam nadzieję, że reguły ci pomogą. Oparte na zestawie wyzwalaczy jest dość łatwe dzięki pewnym regulacjom. Uczynienie go idempotentnym zwykle wymaga więcej przemyśleń na temat sposobu implementacji procedur składowanych.
Jeśli masz więcej sugestii lub zasad, którymi możesz się podzielić, odpal komentarze!