Helion - "Delphi 4. Tworzenie systemów baz danych - księga eksperta", rozdział 11
Rozdział z książki "Delphi 4. Tworzenie systemów baz danych - księga eksperta". © 1999 Wydawnictwo Helion
Rozdział 11.
Procedury zapamiętane i wyzwalacze
Arkadiusz Jakubowski
Często będziesz w sytuacji, kiedy operacja, jaką chcesz wykonać na danych serwera SQL, będzie za bardzo skomplikowana i stwierdzisz, że nie ma możliwości zawarcia jej w jednym poleceniu SQL. Wiele serwerów SQL, w tym InterBase, dostarcza jednak rozwiązań w postaci procedur zapamiętanych. Rezydują one na serwerze i tam są również wykonywane. O korzyściach, jakie płyną z przetwarzania skomplikowanych operacji na serwerze, chyba nie muszę za wiele mówić. Najważniejszą korzyścią chyba jest jednak ograniczenie wielokrotnego transferu danych w sieci. Serwer Interbase, jak również wiele innych serwerów SQL, jak Oracle czy Informix, umożliwia tworzenie procedur zapamiętanych oraz wyzwalaczy. Służy do tego język specyficzny dla każdego serwera. Język ten umożliwia tworzenie pętli, pozwala wykorzystać instrukcje warunkowe jak również wiele innych operacji, które umożliwia każdy język strukturalny.
Interbase pozwala tworzyć dwa rodzaje procedur zapamiętanych. Jeden z nich to procedury wykonawcze oraz wybierające. W następnych sekcjach przedstawię najważniejsze elementy języka procedur zapamiętanych oraz opiszę ich tworzenie. Musisz pamiętać, że język, jakim posługujesz się przy tworzeniu procedur zapamiętanych w Interbase, może się różnić od języka stosowanego np. w Oracle.
Wyzwalacze działają automatycznie gdy wystąpi zdarzenie.
Tworzenie procedur zapamiętanych
Procedury zapamiętane tworzy się za pomocą wyrażenia SQL - CREATE PROCEDURE. Wyrażenie to zbudowane jest z nagłówka oraz bloku kodu. Nagłówek zawiera nazwę procedury, która musi być unikatową nazwą w granicach jednej bazy danych. W nagłówku procedury definiuje się również parametry wejściowe oraz wyjściowe. Blok kodu procedury zapamiętanej zawiera listę zmiennych lokalnych, a zaraz po nich znajduje się kod właściwy otoczony słowami kluczowymi BEGIN ... END. Jak już wspomniałem kod właściwy definiuje się w języku właściwym dla implementacji serwera, w naszym przypadku będzie to język serwera Interbase.
W następnych sekcjach opiszę najważniejsze elementy składni tego języka. Jeśli chcesz dowiedzieć się więcej na temat jego możliwości, musisz zapoznać się z załączoną do Interbase dokumentacją.
Polecenie SET TERM
Procedury zapamiętane tworzy się w postaci skryptu. Nie ma możliwości tworzenia ich w ISQL. Gdy budujesz procedurę zapamiętaną, jesteś również zmuszony zmienić znak kończący instrukcję. Umożliwi to oddzielanie poszczególnych wierszy kodu procedury znakiem średnika.
Do zmiany znaku końca instrukcji służy polecenie SET TERM. Na przykład:
SET TERM ^ ;
Po zakończeniu definiowania procedury zapamiętanej należy powrócić do znaku poprzedniego poleceniem:
SET TERM ; ^
Zmiana znaku końca instrukcji dotyczy również wyzwalaczy. Poniżej znajduje się przykładowy kod, zawierający dwie definicje wyzwalaczy, który ilustruje umiejscowienie przedefiniowanego znaku końca instrukcji.
SET TERM ^ ;
CREATE TRIGGER TI_POZYCJE FOR POZYCJE
ACTIVE BEFORE INSERT POSITION 0
AS
DECLARE VARIABLE numrows INTEGER;
BEGIN
...
END
^
CREATE TRIGGER TU_POZYCJE FOR POZYCJE
ACTIVE BEFORE UPDATE POSITION 0
AS
DECLARE VARIABLE numrows INTEGER;
BEGIN
...
END
^
COMMIT WORK ^
SET TERM ; ^
Wykonywanie i usuwanie procedur zapamiętanych
Zbudujemy prostą procedurę zapamiętaną, której zadaniem będzie podwyższenie cen w tabeli TOWARY o 10 %. Oczywiście zadanie to możesz wykonać bez użycia procedury. Nie chodzi mi tutaj o przedstawienie konkretnego zastosowania procedur, lecz jedynie o zasadę posługiwania się nimi. Przykłady zastosowania procedur zapamiętanych znajdziesz w dalszej części rozdziału. Oto skrypt zawierający jej kod:
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
SET TERM ^ ;
CREATE PROCEDURE PODWYZKA AS
BEGIN
UPDATE TOWARY SET CENA = CENA + ((CENA * 10) / 100);
END ^
SET TERM ; ^
COMMIT WORK;
Zapisz ten kod do pliku tekstowego i nadaj mu rozszerzenie *.sql. Uruchom ISQL i wykonaj polecenie uruchomienia skryptu File | Run an ISQL Script. Kiedy procedura zapamiętana zostanie już dodana do bazy danych, połącz się z nią i uruchom procedurę poleceniem:
EXECUTE PROCEDURE PODWYZKA;
Nastąpi wygenerowanie procedury, która podniesie wartości w polu CENA wszystkich towarów o 10 %. Możesz zobaczyć efekt jej działania przez wydanie polecenia:
SELECT * FROM TOWARY;
Zobaczysz, że wszystkie ceny zostały zmienione.
Jeżeli chcesz usunąć procedurę zapamiętaną, musisz wydać polecenie:
DROP PROCEDURE PODWYZKA;
Deklarowanie zmiennych
Interbase umożliwia definiowanie zmiennych lokalnych działających wewnątrz procedury, tak jak każdy język strukturalny. Poniżej znajduje się przykład dwóch deklaracji zmiennych. Pierwsza z nich, V_CENA, jest zmienną typu numerycznego, druga zmienna, V_VAT, jest typu całkowitego. Przykład zastosowania zmiennych lokalnych w procedurach zapamiętanych znajdziesz w dalszych sekcjach.
DECLARE VARIABLE V_CENA NUMERIC(9, 2);
DECLARE VARIABLE V_VAT INTEGER;
Parametry procedur zapamiętanych
Tak jak wszystkie procedury, które możesz budować w każdym języku strukturalnym, procedury zapamiętane mogą również być wyposażone w parametry, do których przekazuje się odpowiednie wartości. Generalnie procedury zapamiętane byłyby zbędnym dodatkiem do SQL, gdyby nie możliwość wywoływania ich z odpowiednimi parametrami: Poniżej znajduje się przykładowy nagłówek procedury z parametrami :
CREATE PROCEDURE WSTAW_TOWAR(P_NAZWA CHAR(80), P_SWW >
CHAR(8), P_CENA NUMERIC(9, 2), P_VAT INTEGER)
W kodzie procedury z parametrami należy się do nich odwoływać przez nazwę poprzedzoną dodatkowo dwukropkiem. Przykładowy skrypt z procedurą z parametrami znajduje się na poniższym wydruku.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
SET TERM ^ ;
CREATE PROCEDURE WSTAW_TOWAR(P_NAZWA CHAR(80), P_SWW
CHAR(8), P_CENA NUMERIC(9, 2), P_VAT INTEGER)
AS
BEGIN
INSERT INTO TOWARY(NAZWA, SWW, CENA, VAT)
VALUES (:P_NAZWA, :P_SWW, :P_CENA, :P_VAT);
END ^
SET TERM ; ^
COMMIT WORK;
Przykład dodania nowego rekordu z danymi o towarze widzisz poniżej:
EXECUTE PROCEDURE WSTAW_TOWAR('CD-ROM Aztech', 'KWiu 3', 300, 22)
Procedury zwracające pojedynczą wartość
Procedury zapamiętane mogą również zwracać pojedyncze wartości. W poniższym przykładzie procedura zapamiętana zwraca datę ostatniego zamówienia złożonego przez klienta wskazanego w parametrze wywołania procedury. Kluczowym słowem przy konstrukcji tego typów procedur jest RETURNS, po którym definiujemy listę parametrów oraz typ do których zwrócone będą obliczone wartości.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
SET TERM ^ ;
CREATE PROCEDURE DATA_OST_ZAM(PLATNIK INTEGER)
RETURNS (DATA_OST DATE)
AS
DECLARE VARIABLE V_DATA DATE;
BEGIN
SELECT MAX(DATA_WYST) FROM DOKUMENTY
WHERE PLATNIK = :PLATNIK
INTO :V_DATA;
DATA_OST = :V_DATA;
END ^
SET TERM ; ^
COMMIT WORK;
Wykonanie powyższej procedury zapamiętanej poleceniem:
EXECUTE PROCEDURE DATA_OST_ZAM(1)
zwróci wartość daty, jaką widać poniżej:
DATA_OST
===========
1-MAR-1999
Instrukcja FOR SELECT ... DO
Instrukcja FOR SELECT ... DO wykonuje blok kodu lub wyrażenie w odniesieniu do każdego rekordu zwróconego przez wyrażenie SELECT.
Poniższy, następny przykład procedury zapamiętanej z parametrem wykonuje operację podwyżki cen w tabeli TOWARY o zadany procent. Zadanie to wykonywała również procedura przedstawiona wcześniej, jednak w tamtym przypadku wartość procentową, o jaką ceny miały się zwiększyć, musiałeś określić w kodzie procedury. W przykładzie prezentowanym tutaj wartość procentową wskazujesz w parametrze wywołania. Oto skrypt tej procedury.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
SET TERM ^ ;
CREATE PROCEDURE PODWYZKA(PROCENT INTEGER)
AS
DECLARE VARIABLE V_CENA NUMERIC(9, 2);
DECLARE VARIABLE V_PROCENT NUMERIC(9, 2);
DECLARE VARIABLE V_TOWAR INTEGER;
BEGIN
V_PROCENT = 0;
FOR SELECT TOWAR, CENA FROM TOWARY INTO :V_TOWAR, :V_CENA
DO
BEGIN
V_PROCENT = (:V_CENA * :PROCENT) / 100;
UPDATE TOWARY SET CENA = CENA + :V_PROCENT
WHERE
TOWAR = :V_TOWAR;
END
END ^
SET TERM ; ^
COMMIT WORK;
Procedury wybierające
Wynikiem wykonania procedur wybierających, jak sama nazwa sugeruje, jest zbiór rekordów, a nie jak w dotychczas opisywanych procedurach pojedyncza wartość. Kluczowy w tych procedurach jest zastosowanie klauzuli FOR SELECT DO oraz polecenia SUSPEND. Polecenie SUSPEND jest odpowiedzialne za poprawne zwracanie rekordów. Klauzulę FOR SELECT DO poznałeś już wcześniej w poprzednim punkcie. Musisz mieć na uwadze, że polecenia SUSPEND nie możesz użyć, gdy będziesz budował kod dla wyzwalacza. O wyzwalaczach napiszę w następnym rozdziale.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
SET TERM ^ ;
CREATE PROCEDURE INFO_PLATNIK(MIASTO CHAR(40))
RETURNS (NAZWISKO CHAR(40), FIRMA CHAR(80))
AS
BEGIN
FOR SELECT NAZWISKO, FIRMA FROM PLATNIK
WHERE MIEJSCOWOSC = :MIASTO
INTO :NAZWISKO, :FIRMA
DO
SUSPEND;
END ^
SET TERM ; ^
COMMIT WORK;
Oto przykładowy wynik wykonania powyższej procedury następującym poleceniem:
SELECT NAZWISKO, FIRMA FROM INFO_PLATNIK('Kraków')
NAZWISKO FIRMA
============== ====================
Janowski Janek S.C.
Kowalski Kowal S.C.
Zwróć uwagę na polecenie, które należy wydać, aby wykonać procedurę wybierającą. Taka procedura nie jest przeprowadzona po wykonaniu komendy EXECUTE PROCEDURE, lecz należy ją traktować jak zapytanie. Parametry wyjściowe procedury musisz traktować jak pola tabeli w zapytaniu. Możesz tę procedurę potraktować jako tabelę i wydać na przykład następujące polecenie SQL:
SELECT NAZWISKO, FIRMA FROM INFO_PLATNIK('Kraków')
WHERE NAZWISKO LIKE '%ski%'
Obsługa procedur zapamiętanych w Delphi- klasa TStoredProc
Do obsługi procedur zapamiętanych znajdujących się w serwerze bazy danych służy komponent klasy TStoredProc.
Jak już wiesz, procedura zapamiętana przechowywana jest w bazie danych jako jeden z wielu rodzajów obiektów bazy. Tymi obiektami mogą być tabele, indeksy, domeny, jak również procedury zapamiętane. Procedury zapamiętane służą do wykonywania zadań, które są często wykonywane na bazie danych. Wynik wykonania zadania przez procedurę zapamiętaną zwracany jest do aplikacji klienta.
Tak jak tabelom czy innych obiektom serwera bazy danych, również procedurom zapamiętanym Delphi dostarcza komponentu do jego obsługi. Jest nim komponent klasy TStoredProc, który znajduje się wraz z innymi komponentami do obsługi danych bazy danych na zakładce Data Access w palecie komponentów.
Jak wiesz, większość procedur zapamiętanych wymaga podania parametrów wejściowych, których wartości używane są podczas wykonywania procedury. Klasa TStoredProc udostępnia właściwość Params, która pozwala ustawić te parametry przed wykonaniem procedury zapamiętanej. Jeśli chodzi o typ oraz funkcje jakie wykonuje, właściwość ta jest identyczna z właściwością Params dla klasy TQuery.
Właściwość Params używana jest nie tylko do przyjmowania wartości dla parametrów procedur zapamiętanych, ale również do przechowywania zwróconego wyniku wykonania procedury zapamiętanej. W zależności od serwera, procedura zapamiętana może zwrócić pojedynczy zbiór wartości jak również zbiór wartości, podobny do zbioru będącego wynikiem wykonania zapytania.
Nazwę procedury zapamiętanej musisz wskazać we właściwości StoredProcName. W inspektorze obiektów, w czasie projektowania aplikacji zaraz po połączeniu z bazą danych właściwość ta pozwoli Ci wybrać nazwę procedury z listy dostępnych procedur na serwerze.
Przygotowanie i wywoływanie procedur zapamiętanych
W zależności od rodzaju, procedury zapamiętane wywoływane są różnymi sposobami. W tej sekcji zapoznasz się z jednym z nich. W dalszych sekcjach znajduje się opis pozostałych. Koniecznie musisz poznać je wszystkie, aby w pełni móc wykorzystać z ich możliwości.
Klasa TStoredProc jest potomkiem klasy TDataSet, podobnie jak klasy TTable czy TQuery. Wszystkie one zawierają właściwość Active, ale nie należy jej używać w klasie TStoredProc w czasie projektowania aplikacji.
Aby wykonać procedurę zapamiętaną, należy wywołać metodę ExecProc. Przed wykonaniem procedury zapamiętanej należy ją przygotować. Służy do tego metoda Prepare,
Ściślej mówiąc, metoda Prepare przed wykonaniem procedury wiąże jej parametry, po czym inicjuje BDE oraz informuje serwer o gotowości procedury. Zadanie pobierania wartości wejściowych parametrów procedury wykonuje metoda ExecProc. Parametry pobierane są z właściwości Params.
Jeżeli procedura zapamiętana zwraca parametry wyjściowe, to są one zapisywane do właściwości Params, gdy tylko metoda ExecProc zakończy swoje działanie. Wartości zwróconych parametrów można odczytać za pomocą właściwości Params lub metody ParamByName.
Musisz pamiętać, że każda procedura zapamiętana musi być przed wykonaniem przygotowana. Do sprawdzenia, czy jest przygotowana, służy właściwość Prepared. Jeżeli właściwość Prepared zwróci wartość True, oznaczać to będzie że procedura zapamiętana jest przygotowana do uruchomienia.
Jeżeli nie przygotujesz procedury do jej wykonania, zrobi to za Ciebie Delphi. Delphi automatycznie przygotowuje procedurę zapamiętaną, jeżeli nie jest przygotowana w chwili gdy wywołujesz metodę ExecProc.
Jeżeli właściwość Prepared ustawiona jest na wartość True i zmieniana na wartość False, to wywoływana jest metoda UnPrepare, która określa procedurę jako nieprzygotowaną do wykonania. Oprócz tego metoda UnPrepare zwalnia zasoby zaalokowane w celu przygotowania procedury zapamiętanej. Jeżeli właściwość Prepared ustawiona jest na wartość False i zmieniana na wartość True, to wywoływana jest metoda Prepare, która z kolei przygotowuje procedurę zapamiętaną do wykonania.
Zaleca się jednak stosowanie bezpośrednio metod oraz Unprepare. Metody te automatycznie ustawiają właściwość Prepared na odpowiednia wartość.
Obsługa parametrów procedur
Parametry procedury zapamiętanej przechowywane są we właściwości Params. W niej przechowywane są zarówno parametry wejściowe, jak i wyjściowe. Właściwość ta jest typu TParams, który już poznałeś, gdy opisywałem klasę TQuery reprezentującą zapytanie. Jeżeli dobrze znasz klasę TQuery oraz jej właściwości i metody, to mogę powiedzieć, że znasz już klasę TStoredProc.
Dostęp do parametrów procedury daje również metoda ParamByName. Jest ona częściej używana w kodzie programu podczas dostępu do parametrów niż właściwość Params. Deklaracja metody ParamByName jest następująca:
Function ParamByName(Const Value: String): TParam;
Parametr Value określa nazwę parametru, do którego chcesz uzyskać dostęp. Metoda ParamByName używana jest do ustawiania wartości parametrów wejściowych, jak i wyjściowych, zwróconych w wyniku wykonania procedury.
Dzięki właściwości ParamBindMode możesz określić porządek przydzielania parametrów we właściwości Params dla procedury zapamiętanej. Właściwość ParamBindMode może przyjąć jedną z poniższych wartości:
pbByName
Parametry wyszczególniane we właściwości Params pasują pod względem nazwy do parametrów serwera. Wartość ta jest domyślna.
pbByNumber
Parametry wyszczególniane we właściwości Params są przydzielane w kolejności, w jakiej są zdefiniowane w procedurze zapamiętanej.
Zawsze gdy to możliwe, zaleca się ustawianie właściwości ParamBindMode na wartość pbByName. Takie ustawienie gwarantuje, że parametry będą pasować do parametrów używanych przez procedurę zapamiętaną niezależnie od fizycznego porządku we właściwości Params.
W niektórych przypadkach BDE nie zna nazw parametrów procedur zapamiętanych. Tak jest w przypadku serwera Informix. W takiej sytuacji konieczne staje się ustawienie właściwości ParamBindMode na wartość pbByNumber. Oznacza to, że musisz znać typy wszystkich parametrów, aby móc przekazać do nich prawidłowe wartości.
Liczbę wszystkich parametrów procedury przechowywanych we właściwości Params odczytasz z właściwości ParamCount.
Użycie klasy TQuery do odczytania wyniku procedury wybierającej
Procedura wybierająca zwraca pewien zbór rekordów, który należy traktować jak zwykły zbiór danych. Aby wydobyć dane z tego typu procedury, należy posłużyć się komponentem zapytania klasy TQuery, ponieważ parametry wyjściowe procedury należy traktować jak pola tabeli w zapytaniu. Poniższa procedura dla serwera Interbase zwraca z tabeli PLATNIK nazwiska oraz nazwy firm z miejscowości wskazanej w parametrze wywołania procedury. Dalej zobaczysz, jakiego wyrażenia możesz użyć w komponencie zapytania TQuery, aby wydobyć dane z tej procedury.
CREATE PROCEDURE INFO_PLATNIK (MIASTO CHAR(40))
RETURNS (NAZWISKO CHAR(40), FIRMA CHAR(80))
AS
BEGIN
FOR SELECT NAZWISKO, FIRMA FROM PLATNIK
WHERE MIEJSCOWOSC = :MIASTO
INTO :NAZWISKO, :FIRMA
DO
SUSPEND;
END
Procedurę taką traktujemy jako tabelę, więc możemy użyć następującego przykładowego wyrażenia SQL:
SELECT NAZWISKO, FIRMA FROM INFO_PLATNIK(:MIASTO)
lub bardziej rozbudowanego, np. z jednym kryterium:
SELECT NAZWISKO, FIRMA FROM INFO_PLATNIK('Kraków')
WHERE NAZWISKO LIKE '%ski%'
Pierwsze wyrażenie SELECT zawiera parametr :MIASTO, do którego musisz przekazać wartość określającą miejscowość, z której chcesz poznać wszystkich płatników. Parametr :MIASTO jest tutaj parametrem zapytania.
Możesz wprowadzić to wyrażenie do właściwości SQL w komponencie TQuery i sprawdzić właściwość Params, w której powinieneś zobaczyć ten parametr. Oto kod metody, który wykonuje wyrażenie SQL wywołujące naszą procedurę zapamiętaną:
With Query1 Do
Begin
Close;
Try
ParamByName('MIASTO').AsString := EditMiasto.Text;
Open;
Except
On EDBEngineError Do
MessageDlg('Bledna skladnia.', mtError, [mbOk], 0);
On EDatabaseError Do
MessageDlg('Bledny parametr.', mtError, [mbOk], 0);
End;
End;
Obsługa procedur zapamiętanych Oracle oraz Sybase
Klasa TStoredProc została skonstruowana tak, aby mogła obsłużyć wiele procedur zapamiętanych tworzonych na wielu platformach serwerów SQL. Niektóre z tych serwerów posiadają specyficzny język do konstruowania procedur zapamiętanych.
Jednym z tych serwerów SQL jest Oracle, który dostarcza mechanizmu przeładowania funkcji i procedur. Serwer Oracle odróżnia przeładowane procedury poprzez przydzielenie każdej z tych procedur unikatowej wartości numerycznej. Klasa TStoredProc posiada właściwość Overload, za pomocą której możesz określić tę unikatową wartość, aby wskazać, która procedura powinna zostać wykonana.
Domyślnie, właściwość Overload ustawiona jest na wartość zero. Jest to przypuszczenie, że na serwerze nie występują żadne przeładowane procedury. Nie należy zmieniać tej wartości dla żadnego serwera za wyjątkiem Oracle. Jeżeli wywołujesz jedną z przeładowanych metod na serwerze Oracle bez ustawienia wartości dla właściwości Overload, system BDE wykona pierwszą.
Jeżeli właściwość Overload ustawiona jest na wartość 1, BDE wykona pierwszą przeładowaną procedurę, jeżeli wartość ta będzie równa 2, wykonana zostanie druga procedura, itd.
Musisz pamiętać, że przeładowane procedury posiadają te same nazwy, ale różne parametry. Musisz znać parametry każdej z tych procedur, aby przekazać do nich poprawne wartości.
Innym specyficznym serwerem SQL dostępnym na rynku jest Sybase. W odróżnieniu od wszystkich innych serwerów, procedura zapamiętana serwera Sybase nie zwraca automatycznie wyniku wykonania do klienta. Aby wynik został zwrócony do klienta, należy wywołać metodę GetResults klasy TStoredProc. Ściślej mówiąc, zastosowanie metody GetResults powoduje, że zwrócone zostaną wartości parametrów wyjściowych.
O innych specyficznych ograniczeniach i wymaganiach musisz dowiedzieć się z dokumentacji danego serwera. Dokumentacja tego typu (Language Reference) to naprawdę miła lektura.
Przykład obsługi procedur zapamiętanych w Delphi
W przykładzie, który tutaj prezentuję, znajdziesz kody dwóch metod. Każda z nich wywołuje dwie różne procedury zapamiętane. Pierwsza z tych procedur została już opisana w sekcji opisującej użycie komponentu klasy TQuery do odczytania wyniku procedury wybierającej. Druga z kolei zwraca pojedynczy wynik i w celu jej wywołania zbudowana została inna metoda.
Kod drugiej procedury zwracającej pojedynczy wynik, został zamieszczony i opisany w sekcji opisującej procedury zapamiętane zwracające pojedynczą wartość.
Do wykonania tej procedury użyłem komponentu klasy TStoredProc. Przed wykonaniem procedury przekazana została wartość parametru PLATNIK. Ta wartość pobrana została z tabeli PLATNIK wyświetlanej w formularzu:
ParamByName('PLATNIK').AsInteger :=
Table1.FieldByName('PLATNIK').AsInteger;
Natomiast po wykonaniu procedury metodą ExecProc, wartości zwrócone z procedury zostały odczytane z parametru wyjściowego i wyświetlone w wierszu wprowadzania danych EditOstZam klasy TEdit:
EditOstZam.Text := ParamByName('DATA_OST').AsString;
Rysunek 11.1. Okno aplikacji ilustrującej obsługę procedur zapamiętanych
Poniżej znajduje się kod modułu formularza naszego przykładu.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Grids, DBGrids, Db, DBTables, ExtCtrls;
type
TForm1 = class(TForm)
Database1: TDatabase;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
ButtonProc1: TButton;
EditMiasto: TEdit;
ButtonProc2: TButton;
Query1: TQuery;
Label1: TLabel;
DBGrid2: TDBGrid;
DataSource2: TDataSource;
DataSource3: TDataSource;
Table1: TTable;
Label3: TLabel;
Label2: TLabel;
EditOstZam: TEdit;
StoredProc1: TStoredProc;
Bevel1: TBevel;
Bevel2: TBevel;
Label4: TLabel;
Label5: TLabel;
procedure ButtonProc1Click(Sender: TObject);
procedure ButtonProc2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.ButtonProc1Click(Sender: TObject);
begin
With Query1 Do
Begin
Close;
Try
ParamByName('MIASTO').AsString := EditMiasto.Text;
Open;
Label2.Caption := 'Platnicy z miasta : ' + EditMiasto.Text;
Except
On EDBEngineError Do
MessageDlg('Bledna skladnia.', mtError, [mbOk], 0);
On EDatabaseError Do
MessageDlg('Bledny parametr.', mtError, [mbOk], 0);
End;
End;
end;
procedure TForm1.ButtonProc2Click(Sender: TObject);
begin
With StoredProc1 Do
Begin
Close;
Try
ParamByName('PLATNIK').AsInteger :=
Table1.FieldByName('PLATNIK').AsInteger;
ExecProc;
EditOstZam.Text := ParamByName('DATA_OST').AsString;
Except
On EDBEngineError Do
MessageDlg('Bledna skladnia.', mtError, [mbOk], 0);
On EDatabaseError Do
MessageDlg('Bledny parametr.', mtError, [mbOk], 0);
End;
End;
end;
end.
Tworzenie wyzwalaczy
Wyzwalacze tworzy się za pomocą wyrażenia SQL - CREATE TRIGGER. Podobnie jak w procedurach zapamiętanych, wyrażenie wyzwalacza zbudowane jest z nagłówka oraz bloku kodu. Nagłówek wyzwalacza zawiera jego nazwę (unikatową w obrębie bazy danych) oraz nazwę tabeli, w której wyzwalacz będzie wywoływany. Zawiera również wyrażenie określające, kiedy wyzwalacz zostanie uruchomiony. Skróconą składnię wyrażenia SQL tworzącego wyzwalacz znajdziesz w sekcji "Określenie czasu wywołania wyzwalacza", w dalszej części rozdziału.
Użycie generatorów
Generatory w serwerach baz SQL pełnią prawie tę samą rolę co pola automatycznie zwiększające swoją wartość w tabelach Paradox. Są używane w celu zapewnienia unikatowej wartości w polu będącym kluczem głównym dla tabeli. Pola automatycznie zwiększają swoją wartość, kiedy dodawany jest nowy rekord. W serwerze Interbase wartość ta jest dodawana za pomocą funkcji GEN_ID() w wyzwalaczu, który należy zbudować i który będzie wywoływany przed wstawieniem nowego rekordu. Funkcja GEN_ID() posiada dwa parametry. Pierwszy to nazwa generatora, drugi z kolei to wartość, o jaką zwiększana będzie wartość generatora.
Generator tworzymy za pomocą wyrażenia CREATE GENERATOR, np.:
CREATE GENERATOR NUM_PLATNIK_GEN;
Serwer Interbase nie umożliwia użycia wyrażenia typu DROP do usunięcia generatora. Każdy nowo utworzony generator, jest dodawany do tabeli systemowej o nazwie RDB$GENERATORS. Jeżeli chcesz usunąć generator musisz po prostu wydać polecenie jego usunięcia właśnie z tej tabeli systemowej, np.:
DELETE FROM RDB$GENERATORS WHERE
RDB$GENERATOR_NAME = 'NUM_PLATNIK_GEN';
Możesz określić, od jakiej wartości generator zacznie zwiększać swoją wartość. Służy do tego celu polecenie SET GENERATOR. Oto przykład:
SET GENERATOR NUM_PLATNIK_GEN TO 100;
Jeżeli nie wydasz powyższego polecenia, generator będzie zwiększał swoją wartość począwszy od wartości 0.
Generowanie unikatowej wartości dla pola
Poniżej przedstawiam kod wyzwalacza, który automatycznie umieści unikatową wartość w polu PLATNIK w tabeli PLATNIK, gdy tylko wygenerujesz polecenie wstawienia nowego rekordu INSERT.
CREATE TRIGGER SET_NUM_PLATNIK FOR PLATNIK
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.PLATNIK = GEN_ID(NUM_PLATNIK_GEN, 1);
END
Zmienna NEW oraz OLD
Podobnie jak w mechanizmie buforowania zmian, język Interbase służący do budowania procedur zapamiętanych oraz wyzwalaczy posiada możliwość odwołania się do dotychczasowej oraz nowej wartości pola.
W tym rozdziale spotkałeś się już nieraz ze zmienną NEW. Zmienna kontekstowa NEW odwołuje się do nowej wartości kolumny podczas wykonywania operacji INSERT lub UPDATE. Drugą ważną zmienną jest OLD, która określa bieżącą wartość kolumny podczas wykonywania operacji INSERT lub UPSATE. Mówiąc o bieżącej wartości, mam na myśli dotychczasową wartość pola.
Zmienne kontekstowe NEW oraz OLD są często używane do porównywania wartości kolumny przed i po modyfikacji. Mogą one być używane wszędzie tam, gdzie używa się zwykłych zmiennych.
Sprawdzanie więzów integralności referencyjnej za pomocą wyzwalaczy
Poniższy kod wyzwalacza zostanie wygenerowany przed wstawieniem nowego rekordu do tabeli DOKUMENTY. Przy definiowaniu nowego dokumentu, czyli gdy wystawiasz nową fakturę, określasz płatnika, któremu dokument ma zostać wystawiony. Ważne jest więc sprawdzenie, czy dane płatnika o wskazanym numerze, który określasz w tabeli DOKUMENTY, znajdują się w tabeli PLATNIK. Zadanie to realizuje poniższy wyzwalacz:
CREATE TRIGGER RI_I_DOKUM_PLATNIK FOR DOKUMENTY
ACTIVE BEFORE INSERT POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.PLATNIK IS NOT NULL) THEN
BEGIN
SELECT COUNT (*)
FROM PLATNIK
WHERE PLATNIK.PLATNIK = NEW.PLATNIK
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_I_PME;
END
END
END
Pierwszym zadaniem, jakie wykonuje powyższy wyzwalacz, jest określenie, czy wpisana wartość płatnika nie jest wartością pustą NULL. Jeżeli nie, to wyszukujemy poleceniem SELECT z tabeli PLATNIK wszystkich płatników o wskazanym numerze. Jeżeli zwrócona wartość wyrażenia SELECT będzie równa 0, czyli gdy w tabeli PLATNIK nie znajduje się żaden płatnik o tym numerze, wywoływany będzie wyjątek.
Określiliśmy tutaj najprostszą operację sprawdzenia poprawności związku, w jakim się znajdują tabele DOKUMENTY oraz PLATNIK. Jest to oczywiście relacja jeden do wielu, która ściśle określa, że na jednego płatnika może być wystawionych wiele dokumentów oraz że płatnik, na którego jest wystawiony dokument, musi istnieć.
Tego typu wyzwalacze możesz określać nie tylko dla sytuacji, gdy dodajesz nowy rekord, ale również, gdy edytujesz lub usuwasz rekord. Gdy usuwasz rekord z tabeli nadrzędnej w związku jeden do wielu, możesz sprawdzić, czy w ten sposób nie zostaną osierocone rekordy z tabeli podrzędnej.
Tworzenie wyjątków
W wyzwalaczu z poprzedniej sekcji wywoływany jest wyjątek o nazwie RI_I_PME. Wyjątki tworzymy poleceniem CREATE EXCEPTION, w którym należy określić nazwę wyjątku oraz tekst, który będzie wyświetlany, gdy zostanie wywołany wyjątek. Oto przykład:
CREATE EXCEPTION RI_I_PME "Rekord w tabeli nadrzędnej nie istnieje.
Nie mogę utworzyć nowego rekordu w tabeli podrzędnej.";
Wyjątki wywołujemy za pomocą polecenia SQL EXCEPTION, po którym wskazujemy nazwę wyjątku. Po nazwie określasz komunikat, który się pojawi, gdy wyjątek zostanie wywołany. Tekst komunikatu nie może przekroczyć 78 znaków.
Wyjątki możesz również zastosować przy budowie procedur zapamiętanych.
Określenie czasu wywołania wyzwalacza
Oto skrócona składnia wyrażenia CREATE TRIGGER:
CREATE TRIGGER nazwa FOR tabela
[BEFORE | AFTER]
[DELETE | INSERT | UPDATE]
AS
BEGIN
END
Budując wyzwalacz musisz określić kiedy zostanie on wywołany. Jak widzisz, jest kilka możliwości, oto one:
BEFORE INSERTprzed wstawieniem nowego rekordu
BEFORE UPDATEprzed zaktualizowaniem rekordu
BEFORE DELETEprzed usunięciem rekordu
AFTER INSERTpo wstawieniu nowego rekordu
AFTER UPDATEpo zaktualizowaniu rekordu
AFTER DELETEpo usunięciu rekordu
Najczęściej wykorzystuje się wyzwalacze przed wykonaniem operacji wstawienia nowego rekordu BEFORE INSERT. W wyzwalaczu, który zostanie wywołany przed tą operacją, możesz wygenerować unikatową wartość dla pola będącego kluczem obcym lub sprawdzić, czy nowo wstawiany rekord nie narusza więzów integralności. Obie te operacje opisałem wyżej. Jak widzisz, wyzwalacze dają naprawdę wiele możliwości i ich opanowanie na pewno przyniesie Ci wiele korzyści.
Obsługa wyzwalaczy a Delphi
Delphi nie obsługuje wyzwalaczy. Rynek dostarcza wielu różnych serwerów SQL, z których każdy obsługuje wyzwalacze w inny sposób. Delphi jest uniwersalnym narzędziem, ale umożliwienie obsługi wyzwalaczy każdego z serwerów byłoby chyba przedsięwzięciem nieopłacalnym dla firmy Borland. Jest jednak sposób na ominięcie tej niedogodności, a przynajmniej dla serwera Interbase.
Głównie chodzi mi tutaj o wyzwalacze generujące unikatową wartość dla pola klucza głównego tabeli. Zadanie przydzielania takiej wartości świetnie jest realizowane przez wyzwalacz, ale może równie dobrze być zrealizowane za pomocą procedury zapamiętanej. Zobacz poniższy fragment skryptu zawierający wyzwalacz generujący unikatową wartość dla pola PLATNIK będącego kluczem głównym w tabeli PLATNIK:
CREATE GENERATOR NUM_PLATNIK_GEN;
SET TERM ^ ;
CREATE TRIGGER SET_NUM_PLATNIK FOR PLATNIK
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.PLATNIK = GEN_ID(NUM_PLATNIK_GEN, 1);
END
^
Poniższa procedura zapamiętana zwraca wartość, która może zostać podstawiona do omówionego wcześniej pola.
CREATE PROCEDURE SET_NUM_PLATNIK
RETURNS (NUM INTEGER)
AS
BEGIN
NUM = GEN_ID(NUM_PLATNIK_GEN, 1);
END
Jeśli chodzi o wykorzystanie procedur zapamiętanych do generowania unikatowej wartości dla klucza, musisz wykonać dodatkowe czynności w aplikacji klienta, które polegają na obsłudze procedury. Za każdym razem, gdy będziesz dodawał nowy rekord, będziesz zmuszony do wywołania, procedury a następnie podstawienia wyniku jej wykonania do wartości pola. Przykładowy fragment kodu, który realizuje to zadanie, może mieć postać jak poniżej.
Procedure TForm1.Button1Click(Sender: TObject);
Begin
With Table1 Do
Begin
Insert;
StoredProc1.ExecProc;
FieldByName('TOWAR').AsInteger :=
StoredProc1.Params[0].AsInteger;
FieldByName('NAZWA').AsString := Edit1.Text;
FieldByName('SWW').AsString := Edit2.Text;
FieldByName('CENA').AsInteger := StrToInt(Edit3.Text);
FieldByName('VAT').AsInteger := StrToInt(Edit4.Text);
Post;
End;
End;
Masz jeszcze drugą możliwość, a mianowicie zbudowanie metody obsługi zdarzenia AfterInsert. Przykład takiej metody widzisz poniżej:
Procedure TForm1.Table1AfterInsert(DataSet: TDataSet);
Begin
StoredProc1.ExecProc;
DataSet.FieldByName('TOWAR').AsInteger :=
StoredProc1.Params[0].AsInteger;
End;
Powyższa metoda ma jednak pewną wadę, a mianowicie nie gwarantuje, że przydzielane wartości będą w każdym rekordzie większe o jeden. Tzn. zdarzenie AfterInsert będzie generować nową wartość przez wywołanie procedury, ale w przypadku gdy anulujesz operację dodawania rekordu wartość ta zostanie stracona. Przy dodaniu nowego rekordu generator w procedurze wyprodukuje nową wartość o jeden większą, co spowoduje powstanie tzw. dziury w numeracji. Nie jest to aż tak ważny problem, ponieważ unikatowość przydzielanych wartości kluczem nadal zostanie zachowana.
Przedstawię teraz najlepszy sposób, który oprócz swojej podstawowej funkcjonalności, jaką jest podstawianie unikatowej wartości dla klucza głównego, omija niedogodność związaną z niepotrzebnym angażowaniem generatora w procedurze zapamiętanej. Sposób ten polega na podstawieniu jakiejkolwiek wartości metoda zdarzenia AfterInsert do pola będącego kluczem głównym w tabeli. Wartość ta naprawdę może być dowolna, właściwa wartość zostanie podstawiona dopiero przed zatwierdzeniem danych do bazy danych metodą obsługi zdarzenia BeforePost. Poniżej znajduje się kod metod obsługi zdarzenia AfterInsert oraz BeforePost :
Procedure TForm1.Table1AfterInsert(DataSet: TDataSet);
Begin
DataSet.FieldByName('TOWAR').AsInteger := 0;
End;
Procedure TForm1.Table1BeforePost(DataSet: TDataSet);
Begin
If (DataSet.State = dsInsert) Then
Begin
StoredProc1.ExecProc;
DataSet.FieldByName('TOWAR').AsInteger :=
StoredProc1.Params[0].AsInteger;
End;
End;
Wybór, czy do generowania unikatowej wartości stosować wyzwalacze, czy procedury zapamiętane, należy do Ciebie. W następnej sekcji znajdziesz skrypty zawierające definicje wyzwalaczy, jak również skrypt z procedurami zapamiętanymi. Możesz utworzyć dwie bazy danych, jedną posługującą się wyzwalaczami, a drugą używającą procedur. Popracuj z nimi i przekonaj się, który sposób jest lepszy.
Skrypty dla bazy FAKiR zawierające definicje wyzwalaczy oraz procedur zapamiętanych
Skrypty, które zamieściłem poniżej, uzupełniają definicję bazy danych systemu fakturowania i sprzedaży ratalnej o definicje wyzwalaczy oraz procedur zapamiętanych.
Skrypt z definicją wyzwalaczy generujących unikatowe wartości dla kluczy głównych
Poniższy skrypt zawiera definicje generatorów oraz wyzwalaczy generujących unikatowe wartości dla kluczy głównych wszystkich tabel znajdujących się w bazie fakturowania i sprzedaży ratalnej FAKiR. Alternatywnie możesz użyć skryptu znajdującego się w następnej sekcji, który zawiera procedury zapamiętane.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
CREATE GENERATOR NUM_PLATNIK_GEN;
CREATE GENERATOR NUM_TOWAR_GEN;
CREATE GENERATOR NUM_DOKUMENT_GEN;
CREATE GENERATOR NUM_POZYCJA_GEN;
CREATE GENERATOR NUM_RATA_GEN;
SET TERM ^ ;
CREATE TRIGGER SET_NUM_PLATNIK FOR PLATNIK
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.PLATNIK = GEN_ID(NUM_PLATNIK_GEN, 1);
END
^
CREATE TRIGGER SET_NUM_TOWAR FOR TOWARY
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.TOWAR = GEN_ID(NUM_TOWAR_GEN, 1);
END
^
CREATE TRIGGER SET_NUM_DOKUMENT FOR DOKUMENTY
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.DOKUMENT = GEN_ID(NUM_DOKUMENT_GEN, 1);
END
^
CREATE TRIGGER SET_NUM_POZYCJA FOR POZYCJE
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.POZYCJA = GEN_ID(NUM_POZYCJA_GEN, 1);
END
^
CREATE TRIGGER SET_NUM_RATA FOR RATY
ACTIVE BEFORE INSERT
POSITION 0
AS
BEGIN
NEW.RATA = GEN_ID(NUM_RATA_GEN, 1);
END
^
COMMIT WORK ^
SET TERM ; ^
Skrypt z definicją procedur zapamiętanych generujących unikatowe wartości dla kluczy głównych
Poniższy skrypt zawiera definicje generatorów oraz procedur zapamiętanych, które generują unikatowe wartości dla kluczy głównych wszystkich tabel znajdujących się w bazie fakturowania i sprzedaży ratalnej FAKiR.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
CREATE GENERATOR NUM_PLATNIK_GEN;
CREATE GENERATOR NUM_TOWAR_GEN;
CREATE GENERATOR NUM_DOKUMENT_GEN;
CREATE GENERATOR NUM_POZYCJA_GEN;
CREATE GENERATOR NUM_RATA_GEN;
SET TERM ^ ;
CREATE PROCEDURE SET_NUM_PLATNIK AS BEGIN EXIT; END ^
CREATE PROCEDURE SET_NUM_TOWAR AS BEGIN EXIT; END ^
CREATE PROCEDURE SET_NUM_DOKUMENT AS BEGIN EXIT; END ^
CREATE PROCEDURE SET_NUM_POZYCJA AS BEGIN EXIT; END ^
CREATE PROCEDURE SET_NUM_RATA AS BEGIN EXIT; END ^
ALTER PROCEDURE SET_NUM_PLATNIK
RETURNS (NUM INTEGER)
AS
BEGIN
NUM = GEN_ID(NUM_PLATNIK_GEN, 1);
END
^
ALTER PROCEDURE SET_NUM_TOWAR
RETURNS (NUM INTEGER)
AS
BEGIN
NUM = GEN_ID(NUM_TOWAR_GEN, 1);
END
^
ALTER PROCEDURE SET_NUM_DOKUMENT
RETURNS (NUM INTEGER)
AS
BEGIN
NUM = GEN_ID(NUM_DOKUMENT_GEN, 1);
END
^
ALTER PROCEDURE SET_NUM_POZYCJA
RETURNS (NUM INTEGER)
AS
BEGIN
NUM = GEN_ID(NUM_POZYCJA_GEN, 1);
END
^
ALTER PROCEDURE SET_NUM_RATA
RETURNS (NUM INTEGER)
AS
BEGIN
NUM = GEN_ID(NUM_RATA_GEN, 1);
END
^
SET TERM ; ^
COMMIT WORK ;
Skrypt z definicją wyzwalaczy, stosowany w celuzachowania integralności referencyjnej
Skrypt generujący wyzwalacze dla bazy FAKiR, które zabezpieczają bazę danych przed naruszeniem integralności referencyjnej.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
CREATE EXCEPTION RI_I_PME "Brak rekordu w tablicy nadrzędnej.
Błąd utw. rekordu w tablicy podrzędnej.";
CREATE EXCEPTION RI_U_PME "Brak rekordu w tablicy nadrzędnej.
Błąd modyf. rekordu w tablicy podrzędnej.";
CREATE EXCEPTION RI_U_CMPinC "Nie mogę zmodyfikować rekordu tablicy
nadrzędnej w tablicy podrzędnej.";
CREATE EXCEPTION RI_U_CMP "Rekord w tablicy podrz. nadal istnieje.
Błąd modyf. rekordu w tablicy nadrz.";
SET TERM ^ ;
CREATE TRIGGER RI_I_DOKUM_PLATNIK FOR DOKUMENTY
ACTIVE BEFORE INSERT POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.PLATNIK IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM PLATNIK
WHERE PLATNIK.PLATNIK = NEW.PLATNIK
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_I_PME;
END
END
END
^
CREATE TRIGGER RI_U_DOKUM_PLATNIK_POZYCJE_RATY FOR DOKUMENTY
ACTIVE BEFORE UPDATE POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.PLATNIK IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM PLATNIK
WHERE PLATNIK.PLATNIK = NEW.PLATNIK
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_U_PME;
END
end
IF (OLD.PLATNIK != NEW.PLATNIK) THEN
BEGIN
EXCEPTION RI_U_CMPinC;
END
IF (OLD.DOKUMENT != NEW.DOKUMENT) THEN
BEGIN
SELECT COUNT(*)
FROM POZYCJE
WHERE POZYCJE.DOKUMENT = OLD.DOKUMENT
INTO :ilosc;
IF (ilosc > 0) THEN
BEGIN
EXCEPTION RI_U_CMP;
END
END
IF (OLD.DOKUMENT != NEW.DOKUMENT) THEN
BEGIN
SELECT COUNT(*)
FROM RATY
WHERE RATY.DOKUMENT = OLD.DOKUMENT
INTO :ilosc;
IF (ilosc > 0) THEN
BEGIN
EXCEPTION RI_U_CMP;
END
END
END
^
CREATE TRIGGER RI_I_POZYCJE_DOKUM_TOWARY FOR POZYCJE
ACTIVE BEFORE INSERT POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.DOKUMENT IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM DOKUMENTY
WHERE DOKUMENTY.DOKUMENT = NEW.DOKUMENT
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_I_PME;
END
END
IF (NEW.TOWAR IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM TOWARY
WHERE TOWARY.TOWAR = NEW.TOWAR
INTO :ilosc;
if (ilosc = 0) THEN
BEGIN
EXCEPTION RI_I_PME;
END
END
END
^
CREATE TRIGGER RI_U_POZYCJE_DOKUM_TOWARY FOR POZYCJE
ACTIVE BEFORE UPDATE POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.DOKUMENT IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM DOKUMENTY
WHERE DOKUMENTY.DOKUMENT = NEW.DOKUMENT
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_U_PME;
END
END
IF (NEW.TOWAR IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM TOWARY
WHERE TOWARY.TOWAR = NEW.TOWAR
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_U_PME;
END
END
IF (OLD.DOKUMENT != NEW.DOKUMENT) THEN
BEGIN
EXCEPTION RI_U_CMPinC;
END
IF (OLD.TOWAR != NEW.TOWAR) THEN
BEGIN
EXCEPTION RI_U_CMPinC;
END
END
^
CREATE TRIGGER RI_I_RATY_DOKUM FOR RATY
ACTIVE BEFORE INSERT POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.DOKUMENT IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM DOKUMENTY
WHERE DOKUMENTY.DOKUMENT = NEW.DOKUMENT
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_I_PME;
END
END
END
^
CREATE TRIGGER RI_U_RATY_DOKUM FOR RATY
ACTIVE BEFORE UPDATE POSITION 0
AS
DECLARE VARIABLE ilosc INTEGER;
BEGIN
IF (NEW.DOKUMENT IS NOT NULL) THEN
BEGIN
SELECT COUNT(*)
FROM DOKUMENTY
WHERE DOKUMENTY.DOKUMENT = NEW.DOKUMENT
INTO :ilosc;
IF (ilosc = 0) THEN
BEGIN
EXCEPTION RI_U_PME;
END
END
IF (OLD.DOKUMENT != NEW.DOKUMENT) THEN
BEGIN
EXCEPTION RI_U_CMPinC;
END
END
^
COMMIT WORK ^
SET TERM ; ^
Skrypt z definicją dodatkowych procedur zapamiętanych
Poniższy skrypt zawiera definicję trzech procedur zapamiętanych, z których jedna, o nazwie INFO_PLATNIK, zwraca zbiór rekordów zawierających informacje o płatnikach ze wskazanej miejscowości. Druga procedura, o nazwie DATA_OST_ZAM, zwraca datę ostatniego zamówienia złożonego przez wskazanego płatnika. Procedura o nazwie PODWYZKA z kolei aktualizuje rekordy w tabeli TOWARY, to znaczy podnosi cenę o wartość procentową określoną w parametrze wywołania tej procedury.
CONNECT "C:\FAKIR\FAKIR.GDB" USER "SYSDBA" PASSWORD "masterkey";
SET TERM ^ ;
CREATE PROCEDURE INFO_PLATNIK AS BEGIN EXIT; END ^
CREATE PROCEDURE DATA_OST_ZAM AS BEGIN EXIT; END ^
ALTER PROCEDURE INFO_PLATNIK (MIASTO CHAR(40))
RETURNS (NAZWISKO CHAR(40),
FIRMA CHAR(80))
AS
BEGIN
FOR SELECT NAZWISKO, FIRMA FROM PLATNIK
WHERE MIEJSCOWOSC = :MIASTO
INTO :NAZWISKO, :FIRMA
DO
SUSPEND;
END
^
ALTER PROCEDURE DATA_OST_ZAM (PLATNIK INTEGER)
RETURNS (DATA_OST DATE)
AS
DECLARE VARIABLE V_DATA DATE;
BEGIN
SELECT MAX(DATA_WYST) FROM DOKUMENTY WHERE PLATNIK = :PLATNIK
INTO :V_DATA;
DATA_OST = :V_DATA;
END
^
SET TERM ; ^
COMMIT WORK ;
Podsumowanie
W tym rozdziale poznałeś procedury zapamiętane oraz wyzwalacze. Są to zwyczajne elementy składowe serwera SQL, które wykorzystuje się do realizacji pewnych działań na danych. Delphi zawiera komponent klasy TStoredProc, dzięki któremu możesz łatwo obsługiwać procedury zapamiętane. Inaczej niż procedury, wyzwalacze nie są obsługiwane przez Delphi. W tym rozdziale opisałem sposoby radzenia sobie z tą niedogodnością.
Rozdział z książki "Delphi 4. Tworzenie systemów baz danych - księga eksperta". © 1999 Wydawnictwo Helion
Wyszukiwarka
Podobne podstrony:
system baz danychWprowadzenie do systemów baz danychTworzenie baz danych karta pracyTworzenie interfejsów do baz danych z wykorzystaniem technologiiProjektowanie i tworzenie baz danychAlgorytmy numeryczne w Delphi Księga ekspertaWprowadzenie do baz danychPodstawy baz danych zajecia 2 z SQL Tabela Biblioteka2004 11 Porównanie serwerów relacyjnych baz danych Open Source [Bazy Danych]IWZ 2 Podstawy baz danychAnaliza baz danych na temat materiałów betonopodobnychwięcej podobnych podstron