Podstawy języka SQL - dokończenie TRANSAKCJE 1 Czasami zachodzi potrzeba, aby pewna grupa operacji była nierozłączna tzn. albo wykonane powinny zostać wszystkie albo żadna z nich. Najprostszym przykładem jest wykonanie przelewu między dwoma kontami. UPDATE konto SET Stan = Stan [kwota] WHERE id_konta = [konto_wpłacającego] UPDATE konto SET Stan = Stan + [kwota] WHERE id_konta = [konto_odbiorcy] Gdyby z różnych przyczyn (brak prądu, zawieszenie się systemu operacyjnego itp.) wykonywanie kodu zostałoby przerwane po pierwszej instrukcji, to pieniądze zniknęłyby z konta wpłacającego a nie pojawiły się na koncie odbiorcy, co zachwiałoby integralnością całej bazy. Rozwiązaniem dla tego sytuacji są transakcje. Transakcje muszą być atomowe (ang. Atomic), czyli niepodzielne. Jeżeli wszystkie elementy transakcji zostaną poprawnie przeprowadzone, transakcja jest zatwierdzana (ang. Commit), jeżeli choć jeden z jej elementów się nie uda, wówczas cała transakcja musi zostać odwołana (ang. Rollback). Po zatwierdzeniu transakcji baza nie może zawierać błędnych danych, stąd mowa o spójności (ang. Consistency). Transakcje są również izolowane (ang. Isolated) i trwałe (ang. Durable). Pierwszy z tych terminów oznacza, że zmiany danych dokonane w obrębie transakcji są widoczne na zewnątrz dopiero po jej zatwierdzeniu. Drugi termin oznacza pewność, że wynik zatwierdzonej transakcji jest na dobre zapisywany w systemie i nie zginie nawet w przypadku awarii. Te cztery zapewniane przez każdy system transakcyjny cechy, w języku angielskim tworzą akronim ACID Poziomy izolacji Standard języka SQL definiuje 4 poziomy izolacji transakcji {ang. isolation levels). Kolejne poziomy oznaczają ochronę przed trzema niepożądanymi zjawiskami: odczytem niezatwierdzonych rekordów (ang. dirty read), odczytem niepowtarzalnym (ang. nonrepeatable read) oraz odczytem widm (ang. phantom read). Odczyt niezatwierdzonych rekordów, to sytuacja, w której w obrębie jednej transakcji widać niezatwierdzone zmiany przeprowadzane w innej transakcji. Jeżeli, na przykład, użytkownik zapyta o stan konta w chwili dokonywania przelewu, a przelew z pewnych przyczyn zostanie odwołany, to może się okazać, że użytkownik zobaczył saldo konta pomniejszone o kwotę przelewu. Jeżeli następnie 1 Wg. Michał Kwiatek, Transakcyjność w bazach danych, Software 2.0 12/2001, str 18-21,26
2 użytkownik oprze na tej informacji inne obliczenia, to będą one bez sensu, gdyż przelew nigdy nie doszedł do skutku. Ochrona przed tym zjawiskiem jest stosunkowo prosta opiera się na wykorzystaniu mechanizmu blokad (ang. locks). Prześledźmy sytuację na Rysunku 1. Jeżeli proces 1 zmienia rekord n, to na ten rekord zakładana jest blokada. Inny użytkownik (proces 2) widzi zmieniony rekord w postaci sprzed rozpoczęcia transakcji procesu S. Gdy proces 2 spróbuje zmienić ten sam rekord, wówczas zostanie zablokowany do momentu zatwierdzenia (lub odwołania) zmiany tego rekordu, czyli do chwili zakończenia transakcji procesu 1. Proszę również zauważyć, że zmiana dokonana w procesie 1 jest uwzględniana w obliczeniach procesu 2. Przed odczytem niezatwierdzonych rekordów chroni standardowy poziom izolacji: read committed. Odczyt niepowtarzalny polega na tym, że wyniki wykonania tego samego zapytania w jednej transakcji, ale w pewnym odstępie czasowym różnią się: rekordy z wyniku pierwszego wykonania zapytania nie występują w wyniku powtórnego wykonania, lub wartości rekordów zmieniły się. Aby uniknąć tego zjawiska, należy korzystać z poziomu izolacji repeatable read. Z odczytem widm mamy do czynienia, gdy w wyniku drugiego wykonania tego samego zapytania pojawiły się rekordy, których poprzednio nie było - rekordy widma. Zjawisko to nie występuje, o ile aktualnym poziomem izolacji jest serializable. W praktyce stosuje się głównie zapewniający podstawową poprawność poziom read committed. Co prawda, w standardzie języka SQL za domyślny uznawany jest serializable, ale każdy z kolejnych poziomów izolacji zwiększa obciążenie systemu i ryzyko wystąpienia zakleszczeń (ang. deadlocks), zmniejsza możliwość równoległego dostępu do danych i, co za tym idzie, wydajność systemu. Zazwyczaj poziom read committed rzeczywiście wystarcza, jednakże sytuacje wymagające zastosowania wyższych poziomów izolacji są zupełnie naturalne i wcale nie należą do rzadkości. Instrukcja Select... Into SELECT pole_1[,pole_2[,...]] INTO nowa_tabela FROM źródło pole_1, pole_2 nowa_tabela źródło Nazwy pól, które mają być skopiowane do nowej tabeli. Nazwa tworzonej tabeli.. Jeśli nowa_tabela ma taką samą nazwę jak tabela już istniejąca, to wystąpi błąd. Nazwa istniejącej tabeli, z której wybierane są rekordy. Może to być pojedyncza tabela, wiele tabel lub kwerenda. Tworzenie nowej tabeli z istniejącej SELECT * INTO [Employees Backup] FROM Employees; Tworzenie nowej tabeli i wpisanie do niej wybranych rekordów. SELECT Employees.FirstName, LastName INTO [Sales Representatives] FROM Employees WHERE Title = 'Sales Representative'; Select... Into z dwóch tabel
SELECT Employees.*, Salary INTO Trainees FROM Employees INNER JOIN Payroll ON Employees.EmployeeID =Payroll.EmployeeID WHERE Title = 'Trainee'; Select... Into z polami obliczeniowymi SELECT PRACOWNICY.NAZWISKO, PRACOWNICY.IMIĘ, PRACOWNICY.[KOD DZIAŁU], [STAWKA]*[CZAS PRACY] AS [STAWKA TYGODNIOWA] INTO [Płace tygodniowo] FROM PRACOWNICY ORDER BY PRACOWNICY.NAZWISKO; 3 Instrukcja Insert... Into INSERT INTO cel [(pole_1[,pole_2[,...]])] SELECT [źródło.] pole_1[,pole_2[,...] FROM wyrażenie_tabelowe INSERT INTO cel [(pole_1[,pole_2[,...]])] VALUES (wartość_1[,wartość_2[,...]) cel źródło pole_1, pole_2 Nazwa tabeli lub kwerendy, do której mają być dołączone rekordy. Nazwa tabeli lub kwerendy, z której będą kopiowane rekordy. Nazwy pól, do których będą dołączane dane, jeśli występują po argumencie cel, lub też nazwy pól, z których będą pobierane dane, jeśli występują po argumencie źródło. wyrażenie tabelowe Nazwa tabeli lub tabel, z których pobierane są rekordy. Ten argument może być nazwą pojedynczej tabeli lub strukturą będącą wynikiem operacji INNER JOIN, LEFT JOIN lub RIGHT JOIN, albo też zapisaną kwerendą. wartość_1, wartość_2 Wartości, które mają być umieszczone w określonych polach nowego rekordu. Każda wartość jest umieszczana w polu, które odpowiada pozycji wartości na liście: wartość_1 zostaje umieszczona w polu_1 nowego rekordu, wartość_2 w polu_2, i tak dalej. Wartości muszą być oddzielone przecinkami, zaś pola tekstowe muszą być ujęte w cudzysłów (' '). Dołączenie danych do z jednej tabeli do drugiej istniejącej tabeli INSERT INTO PRACOWNICY SELECT [PRACOWNICY-filia].* FROM [PRACOWNICY-filia]; Dołączenie nowych danych do istniejącej tabeli INSERT INTO Employees (FirstName,LastName, Title) VALUES ('Harry', 'Washington', 'Trainee');
Dołączenie z jednej tabeli do drugiej według wybranych kryteriów 4 INSERT INTO Employees SELECT Trainees.* FROM Trainees WHERE HireDate < Now() - 30; Instrukcja Update UPDATE tabela SET nowa_wartość WHERE kryteria; tabela nowa_wartość kryteria Nazwa tabeli zawierającej dane, które mają zostać zmodyfikowane. Wyrażenie określające wartość, która ma być wstawiona w określonym polu uaktualnianych rekordów. Wyrażenie określające, które rekordy zostaną uaktualnione. Uaktualnianie rekordów spełniających wyrażenie. UPDATE DISTINCTROW [PRACOWNICY-PARKING] SET [PRACOWNICY- PARKING].[OPŁATA] = [OPŁATA]*1.5 WHERE ((([PRACOWNICY-PARKING].PARKING)="pa3")); UPDATE Employees SET ReportsTo = 5 WHERE ReportsTo = 2; Uaktualnianie + INNER JOIN UPDATE Suppliers INNER JOIN Products ON Suppliers.SupplierID = Products.SupplierID SET UnitPrice = UnitPrice *.95 WHERE CompanyName = 'Tokyo Traders' AND Discontinued = No; Union [TABLE] kwerenda_1 UNION [ALL] [TABLE] kwerenda_2 [UNION [ALL] [TABLE] kwerenda_n [... ]] Unie służą do łączenia w jedno zapytanie danych z różnych tabel, połączenie to jednak zasadniczo różni się od INNER (LEFT/RIGHT) JOIN, gdyż scala rekordy z dwóch lub więcej tabel a nie łączy natomiast pól w jeden rekord. Proste połączenie TABLE Pracownicy UNION TABLE [Pracownicy-filia]
5 To samo połączenie inaczej SELECT * FROM pracownicy UNION SELECT * FROM [pracownicy-filia] Unia z warunkiem SELECT * FROM pracownicy WHERE [kod działu]="ad" UNION ALL SELECT * FROM [pracownicy-filia] Klauzula ALL powoduje, że zwracane są wszystkie rekordy, bez niej rekordy powtarzające się byłyby pomijane. Wykorzystanie unii dla danych z jednej tabeli: Załóżmy, że w tabeli wyroby znajdują się informacje o wyprodukowanych wyrobach firmy. Ponieważ przy wytworzeniu jednego wyrobu pracuje maksymalnie 5 pracowników, w tabeli znajdują się pola id_prac1,id_prac2,,id_prac5 zawierające id pracowników, którzy byli zaangażowaniu przy produkcji. Nie ma ustalonej kolejności wpisywania pracowników, ten sam pracownik może przy jednym wyrobie być zapisany w id_prac1, a przy innym w id_prac3, itp. Uwaga: Nie jest to najlepszy sposób zaimplementowania relacji wiele do wiele, o wiele bardziej eleganckim rozwiązaniem byłoby dodanie tabeli pracowicy-wyroby. Zadanie: Policzyć przy produkcji ilu wyrobów był zatrudniony każdy z pracowników. Rozwiązanie z wykorzystaniem unii: SELECT id,count(id_wyrobu) as liczba FROM (SELECT id_prac1 as id,id_wyrobu FROM wyroby WHERE id_prac1<>0 UNION SELECT id_prac2 as id,id_wyrobu FROM wyroby WHERE id_prac2<>0 UNION SELECT id_prac3 as id,id_wyrobu FROM wyroby WHERE id_prac3<>0 UNION SELECT id_prac4 as id,id_wyrobu FROM wyroby WHERE id_prac4<>0 UNION SELECT id_prac5 as id,id_wyrobu FROM wyroby WHERE id_prac5<>0 ) GROUP BY id id_wyrobu id_prac1 id_prac2 id_prac3 id_prac4 id_prac5 1 2 3 4 1 0 2 4 2 3 0 0 3 2 0 1 0 0 4 3 4 0 0 0 5 1 0 0 5 0 Wydajność id liczba 1 3 2 3 3 3 4 3 5 1 Kwerenda krzyżowa Transform Pivot W tabeli pracownicy pole id_zwiazek oznacza klucz obcy do tabeli zwiazki_zawodowe. Naszym zadaniem jest zliczyć działami osoby należące do poszczególnych związków
TRANSFORM Count(PRACOWNICY.IDENTYFIKATOR) AS Czlonkowie SELECT PRACOWNICY.[KOD DZIAŁU], Count(PRACOWNICY.IDENTYFIKATOR) AS [Liczba_osob] FROM PRACOWNICY INNER JOIN zwiazki_zawodowe ON pracownicy.id_zwiazek=zwiazki_zawodowe.id_zwiazek GROUP BY PRACOWNICY.[KOD DZIAŁU] PIVOT zwiazki_zawodowe.nazwa_zwiazku; 6 Związki działami KOD DZIAŁU Liczba_osob OPZZ Solidarność Sierpień 80 Związek zawodowy łysych AD 2 1 1 CH 2 1 1 EE 4 3 1 RE 1 1 SP 2 1 1 TR 1 1 ZA 3 1 1 1 Kwerenda krzyżowa rozbicie na daty: TRANSFORM Sum(Nagrody.kwota) AS SumaOfkwota SELECT Nagrody.id, Sum(Nagrody.kwota) AS [Suma kwota] FROM Nagrody GROUP BY Nagrody.id PIVOT Format([data],"yyyy"); Lub (postać transponowana ) TRANSFORM Sum(Nagrody.kwota) AS SumaOfkwota SELECT format(nagrody.data,"mm"), Sum(Nagrody.kwota) AS [Suma kwota] FROM Nagrody GROUP BY format(nagrody.data,"mm") PIVOT Nagrody.id;