Instrukcja do laboratorium z przedmiotu: Bazy danych Laboratorium nr 3. Metody zarządzania i analizy danych Opracował A. Bujnowski 2010-03-08 Projekt Przygotowanie i realizacja kierunku inżynieria biomedyczna studia międzywydziałowe współfinansowany ze środków Unii Europejskiej w ramach Europejskiego Funduszu Społecznego.
1. Cele laboratorium zapoznanie się z metodami wprowadzania, usuwania i modyfikacji danych. Instrukcje warunkowe. Zapytania złożone i funkcje agregacji 2. Przykładowa baza danych: Jako przykład bazy danych posłuży nam przygotowana uprzednio i nieco zmodyfikowana struktura bazy dla wypożyczalni płyt DVD. Na potrzeby dzisiejszego laboratorium do poprzednio zaprojektowanej struktury tabel dołączymy jeszcze jedną: gatunek oraz dodamy atrybut cena dla każdej płyty. Zakładamy, że płyta należy do jednego gatunku. Kod w języku SQL zakładający bazę danych podana jest poniżej, wytłuszczonym drukiem pokazano zmiany w stosunku do poprzedniej wersji CREATE TABLE klient ( imie varchar(20) not null, nazwisko varchar(40) not null, nr_dowodu char(10), id_klienta serial primary key); CREATE TABLE gatunek( nazwa varchar(30) not null, id_gatunku serial PRIMARY KEY ); CREATE TABLE plyta( tytul varchar(40) not null, numer serial primary key, cena numeric(4,2), gatunek integer REFERENCES gatunek ON DELETE SET NULL ON UPDATE CASCADE); CREATE TABLE wypozyczenie( kto_wypozyczyl int not null REFERENCES klient ON DELETE RESTRICT ON UPDATE RESTRICT, 2
co_wypozyczyl int not null REFERENCES plyta ON DELETE RESTRICT ON UPDATE CASCADE, data_wypozyczenia timestamp default now(), data_zwrotu timestamp, primary key(kto_wypozyczyl, co_wypozyczyl, data_wypozyczenia) ); CREATE TABLE jest_pracownikiem( rabat int, id_klienta int primary key references klient); Dla ujednolicenia sposobu pracy w tym ćwiczeniu, po zalogowaniu do serwera bazy.eti.pg.gda.pl usuń poprzednią bazę poleceniem: dropdb lab2_login gdzie login to Twój login do systemu. Następnie załóż nową bazę o nazwie jak poprzednio poleceniem: createdb lab2_login Teraz przygotuj plik tekstowy z definicją poleceń zakładających bazę danych: mcedit baza3.sql Wypełnij zawartość tego pliku poleceniami z zaprezentowanego wyżej listingu. Połącz się ze swoją bazą danych: psql lab2_login Wczytaj instrukcje z pliku baza1.sql: \i baza3.sql lab2_bujnows=# \i baza3.sql psql:baza3.sql:5: NOTICE: CREATE TABLE will create implicit sequence "klient_id_klienta_seq" for "serial" column "klient.id_klienta" psql:baza3.sql:5: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "klient_pkey" for table "klient" CREATE TABLE psql:baza3.sql:9: NOTICE: CREATE TABLE will create implicit sequence "plyta_numer_seq" for "serial" column "plyta.numer" psql:baza3.sql:9: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "plyta_pkey" for table "plyta" CREATE TABLE psql:baza3.sql:12: ERROR: syntax error at or near "cena" at character 2 3
psql:baza3.sql:19: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "wypozyczenie_pkey" for table "wypozyczenie" CREATE TABLE psql:baza3.sql:23: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "jest_pracownikiem_pkey" for table "jest_pracownikiem" CREATE TABLE psql:baza3.sql:28: NOTICE: CREATE TABLE will create implicit sequence "gatunek_id_gatunku_seq" for "serial" column "gatunek.id_gatunku" psql:baza3.sql:28: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "gatunek_pkey" for table "gatunek" CREATE TABLE lab2_bujnows=# W tej chwili struktura Twojej bazy danych jest gotowa do pracy. Na początku poznamy kilka poleceń na uzupełnienie jej danymi. Na początku jednak sprawdź, czy twoja baza przechowuje właściwą strukturę tabel: \d List of relations Schema Name Type Owner --------+------------------------+----------+--------- public gatunek table bujnows public gatunek_id_gatunku_seq sequence bujnows public jest_pracownikiem table bujnows public klient table bujnows public klient_id_klienta_seq sequence bujnows public plyta table bujnows public plyta_numer_seq sequence bujnows public wypozyczenie table bujnows (8 rows) \d klient Table "public.klient" Column Type Modifiers ------------+----------------------- +---------------------------------------------------------------- imie character varying(20) not null nazwisko character varying(40) not null nr_dowodu character(10) id_klienta integer not null default nextval('public.klient_id_klienta_seq'::text) Indexes: "klient_pkey" primary key, btree (id_klienta) \d plyta Table "public.plyta" Column Type Modifiers ---------+----------------------- +---------------------------------------------------------- tytul character varying(40) not null numer integer not null default nextval('public.plyta_numer_seq'::text) cena numeric(4,2) gatunek integer Indexes: 4
"plyta_pkey" primary key, btree (numer) Foreign-key constraints: "$1" FOREIGN KEY (gatunek) REFERENCES gatunek(id_gatunku) \d gatunek Table "public.gatunek" Column Type Modifiers ------------+----------------------- +----------------------------------------------------------------- nazwa character varying(30) not null id_gatunku integer not null default nextval('public.gatunek_id_gatunku_seq'::text) Indexes: "gatunek_pkey" primary key, btree (id_gatunku) \d jest_pracownikiem Table "public.jest_pracownikiem" Column Type Modifiers ------------+---------+----------- rabat integer id_klienta integer not null Indexes: "jest_pracownikiem_pkey" primary key, btree (id_klienta) Foreign-key constraints: "$1" FOREIGN KEY (id_klienta) REFERENCES klient(id_klienta) \d wypozyczenie Table "public.wypozyczenie" Column Type Modifiers ----------------+-----------------------------+------------------------ kto_wypozyczyl integer not null co_wypozyczyl integer not null d_wypozyczenia timestamp without time zone not null default now() d_zwrotu timestamp without time zone Indexes: "wypozyczenie_pkey" primary key, btree (kto_wypozyczyl, co_wypozyczyl, d_wypozyczenia) Foreign-key constraints: "$1" FOREIGN KEY (kto_wypozyczyl) REFERENCES klient(id_klienta) ON UPDATE RESTRICT ON DELETE RESTRICT "$2" FOREIGN KEY (co_wypozyczyl) REFERENCES plyta(numer) ON UPDATE CASCADE ON DELETE RESTRICT Jeżeli którakolwiek z tabel nie została założona lub jej struktura (poza właścicielem tabeli - owner) jest inna niż ta, pokazana w instrukcji dokonaj niezbędnych poprawek. Może się przydać polecenie DROP TABLE lub ALTER TABLE. 3. Wstawianie danych do tabel. Na poprzednich zajęciach wprowadzone instrukcja wprowadzania danych do bazy danych INSERT INTO tabela VALUES. Sprawdźmy jeszcze raz jak zadziałają poniższe instrukcje: 5
INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Jan','Kowalski','DB230398'); Sprawdź również uproszczoną metodę wprowadzania danych do takiej tabeli: INSERT INTO klient VALUES ('Anna','Nowak'); INSERT INTO klient VALUES ('Ewa','Zielińska','DB232343'); Możliwe jest też dodawanie z wymuszeniem własnej wartości id_klienta ale jest to niezalecane. Sprawdź dlaczego. Odpowiedzią niech będzie poniższy fragment kodu: INSERT INTO klient (imie,nazwisko,nr_dowodu,id_klienta) VALUES ('Jan','Kowal','DB130398',1); Powyższe zapytanie nie powiedzie się, gdyż próbujemy użyć istniejącego id_klienta ponownie. INSERT INTO klient (imie,nazwisko,nr_dowodu,id_klienta) VALUES ('Jan','Kowal','DB130398',1); ERROR: duplicate key violates unique constraint "klient_pkey" Ale możemy poprawnie dodać klienta, z innym id_klienta niż już użyte: INSERT INTO klient (imie,nazwisko,nr_dowodu,id_klienta) VALUES ('Janusz','Kowalski','DB210398',4); INSERT INTO klient (imie,nazwisko,nr_dowodu,id_klienta) VALUES ('Janusz','Kowalski','DB210398',4); Problem pojawi się przy próbie dodania nowego klienta z automatycznym id_klienta: INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Maria','Kowalska','DB230300'); lab2_bujnows=# INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Maria','Kowalska','DB230300'); ERROR: duplicate key violates unique constraint "klient_pkey" lab2_bujnows=# select * from klient; imie nazwisko nr_dowodu id_klienta --------+-----------+------------+------------ Jan Kowalski DB230398 1 Anna Nowak 2 Ewa Zielińska DB232343 3 Janusz Kowalski DB210398 4 (4 rows) Ale w tym przypadku ponowne wykonanie tego samego polecenia już zadziała poprawnie (bo system przyjmie id_klienta jako 5): 6
lab2_bujnows=# INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Maria','Kowalska','DB230300'); INSERT 80519489 1 lab2_bujnows=# select * from klient; imie nazwisko nr_dowodu id_klienta --------+-----------+------------+------------ Jan Kowalski DB230398 1 Anna Nowak 2 Ewa Zielińska DB232343 3 Janusz Kowalski DB210398 4 Maria Kowalska DB230300 5 (5 rows) lab2_bujnows=# Dlatego należy zachowywać ostrożność przy wpisywaniu wartości do kolumn o typie serial lub auto_increment (MySQL). W przypadku konieczności załadowania do bazy danych większej ilości danych przydatne może być wykorzystanie skryptów podobnych do tych, które wykorzystywane były przy zakładaniu bazy danych. Opuść bazę danych (polecenie \q) i wyedytuj plik tekstowy jak załączono poniżej: mcedit klienci.sql INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Adam','Wisniak','AFF456432'); INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Adrian','Wicki','AFF456433'); INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Iwona','Wisniak','AFF456434'); INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Jolanta','Ponicka','AFF456452'); INSERT INTO klient (imie,nazwisko,nr_dowodu) VALUES ('Adam','Brzeski','AFG456432'); INSERT INTO klient(imie,nazwisko,nr_dowodu) VALUES ('Adam','Irenowski','AFF400432'); INSERT INTO klient(imie,nazwisko,nr_dowodu) VALUES ('Jolanta','Wisniak','AFF454432'); Zapisz plik (F2) i opuść edytor (F10). Połącz się ze swoją bazą: psql lab2_login Wczytaj skrypt zewnętrzny poleceniem \i klienci.sql. 7
lab2_bujnows=# \i klienci.sql INSERT 80519490 1 INSERT 80519491 1 INSERT 80519492 1 INSERT 80519493 1 INSERT 80519494 1 INSERT 80519495 1 INSERT 80519496 1 lab2_bujnows=# select * from klient; imie nazwisko nr_dowodu id_klienta ---------+-----------+------------+------------ Jan Kowalski DB230398 1 Anna Nowak 2 Ewa Zielińska DB232343 3 Janusz Kowalski DB210398 4 Maria Kowalska DB230300 5 Adam Wisniak AFF456432 6 Adrian Wicki AFF456433 7 Iwona Wisniak AFF456434 8 Jolanta Ponicka AFF456452 9 Adam Brzeski AFG456432 10 Adam Irenowski AFF400432 11 Jolanta Wisniak AFF454432 12 (12 rows) Jak widać możliwe jest stosunkowo szybkie wczytywanie takich danych. Czasami jednak problematyczne może okazać się wyświetlanie potwierdzeń wczytywanych danych, lub konieczność przygotowania pliku z kompletną składnią w SQL-u. Większość systemów zarządzania bazami danych przychodzi tutaj z pomocą udostępniając interfejs do pobierania danych z pliku. Plik powinien być jednak odpowiednio sformatowany. W PostgreSQL poleceniem takim jest \copy: Copy działa w obie strony pozwala na eksport danych z tabeli do pliku (COPY TO) i z pliku do tabeli (COPY FROM). W tej chwili zajmiemy się tylko importem danych do bazy danych. Plik, który zawiera dane do wgrania powinien być odpowiednio przygotowany. Wgrywając dane do tabeli musi on składać się z wierszy, z których każde pole zawiera dokładnie taką samą ilość separatorów pól. Dwa kolejne separatory pól oznaczają wartość typu NULL. Ostatnia linia nie może składać się z pustych znaków itd. Domyślnym separatorem pól jest znak tabulacji, zaś wierszy znak końca linii. Przygotujmy zatem kilka plików wsadowych do wypełnienia gatunków i płyt: W tym celu opuść bazę danych (\q) i przygotuj pliki: mcedit gatunki.txt Zawartość pliku: komedia dramat musical kreskowka thriller sensacyjny przygodowy 8
historyczny fantasy mcedit plyty.txt Zawartość pliku: Shrek 15 Piraci z Karaibow 20 Calineczka 15 Szeregowiec Dolot 10 Constantine 20 Hellboy 15 UWAGA! Ważne jest aby w pliku plyty.txt pomiędzy tytułem a ceną cisnąć TAB a nie kilka spacji!!! Teraz spróbuj wczytać tak przygotowane dane z pliku. lab2_bujnows=# \copy gatunek(nazwa) from gatunki.txt lab2_bujnows=# select * from gatunek; nazwa id_gatunku -------------+------------ komedia 1 dramat 2 musical 3 kreskówka 4 thriller 5 sensacyjny 6 przygodowy 7 historyczny 8 fantasy 9 (8 rows) lab2_bujnows=# lab2_bujnows=# \copy plyta(tytul,cena) from plyty.txt lab2_bujnows=# select * from plyta; tytul numer cena gatunek -------------------+-------+-------+--------- Shrek 1 15.00 Piraci z Karaibów 2 20.00 Calineczka 3 15.00 Szeregowiec Dolot 4 10.00 Constantine 5 20.00 Hellboy 6 15.00 (6 rows) 4. Aktualizacja danych w tabeli. W tak przygotowanej bazie danych pojawili się klienci, płyty i gatunki. lab2_bujnows=# select * from klient; 9
imie nazwisko nr_dowodu id_klienta ---------+-----------+------------+------------ Jan Kowalski DB230398 1 Anna Nowak 2 Ewa Zielińska DB232343 3 Janusz Kowalski DB210398 4 Maria Kowalska DB230300 5 Adam Wisniak AFF456432 6 Adrian Wicki AFF456433 7 Iwona Wisniak AFF456434 8 Jolanta Ponicka AFF456452 9 Adam Brzeski AFG456432 10 Adam Irenowski AFF400432 11 Jolanta Wisniak AFF454432 12 (12 rows) lab2_bujnows=# select * from plyta; tytul numer cena gatunek -------------------+-------+-------+--------- Shrek 1 15.00 Piraci z Karaibów 2 20.00 Calineczka 3 15.00 Szeregowiec Dolot 4 10.00 Constantine 5 20.00 Hellboy 6 15.00 (6 rows) lab2_bujnows=# select * from gatunek; nazwa id_gatunku -------------+------------ komedia 1 dramat 2 musical 3 kreskówka 4 thriller 5 sensacyjny 6 przygodowy 7 historyczny 8 fantasy 9 (9 rows) lab2_bujnows=# Płyty nie osiadają przypisanych gatunków. Spróbujmy zatem przypisać gatunki do płyt. Dokonamy tego przy pomocy polecenia UPDATE. Ustawmy zatem gatunek komedia dla płyty Shrek. To co trzeba zrobić to sprawdzić id gatunku dla komedii i wpisać to do bazy danych: lab2_bujnows=# select * from gatunek; nazwa id_gatunku -------------+------------ komedia 1 dramat 2 musical 3 10
kreskówka 4 thriller 5 sensacyjny 6 przygodowy 7 historyczny 8 fantasy 9 (9 rows) lab2_bujnows=# select * from gatunek where nazwa='komedia'; nazwa id_gatunku ---------+------------ komedia 1 (1 row) lab2_bujnows=# update plyta set gatunek=1 where tytul='shrek'; UPDATE 1 lab2_bujnows=# select * from plyta; tytul numer cena gatunek -------------------+-------+-------+--------- Piraci z Karaibów 2 20.00 Calineczka 3 15.00 Szeregowiec Dolot 4 10.00 Constantine 5 20.00 Hellboy 6 15.00 Shrek 1 15.00 1 (6 rows) lab2_bujnows=# Aby wykonać to zadanie musieliśmy wykonać dwa zapytania w pierwszym (SELECT) należało sprawdzić id_gatunku dla komedii i drugie polecenie jest faktycznym uzupełnieniem danych (UPDATE). Można zapisać to przy pomocy pojedynczego zapytania. Sprawdźmy to: update plyta set gatunek=(select id_gatunku from gatunek where nazwa = 'kreskowka') where tytul = 'Calineczka'; W podobny sposób przypisz wszystkie filmy do gatunków: Piraci z Karaibów przygodowy Szeregowiec Dolot kreskówka Constantine - thriller Hellboy - fantasy Zastanów się czy w podobny sposób nie da się zapisać wypożyczeń poleceniem INSERT. Spróbuj zapisać akcję wypożyczenia płyty o tytule Constantine dla pana Jana Kowalskiego. W podobny sposób wypożycz płyty użytkownikom: Adrian Wicki : Hellboy Iwona Wisniak : Szeregowiec Dolot Jolanta Ponicka : Constantine Operatory w SQL 11
W języku SQL istnieje szereg operatorów warunkowych do używania z klauzulą WHERE. Są to: Operatory logiczne AND i OR, używane do połączenia warunków. Przykład : SELECT * FROM klient where id_klienta > 5 AND id_klienta < 10; Operatory porównania: > (większe), < (mniejsze), <= (mniejsze lub równe), >= (większe lub równe), <> (różne) W przypadku ciągów tekstowych można wyróżnić operator LIKE, który pozwala na używanie znaków typu wildcard. Znaki te to '_' - zastępuje dowolny jeden znak oraz '%' - zastępuje dowolny ciąg znaków. Przykład: SELECT * FROM klient WHERE imię LIKE 'Ja%'; Operator BETWEEN.. AND - poznamy go poprzez przykład użycia: SELECT * FROM klient where id_klienta BETWEEN 3 AND 7; Operator IN() pozwala wybrać elementy z listy do porównania wartości: SELECT * FROM klient WERE id IN(3,5,9); SELECT * FROM klient WERE imie IN('Jan','Janina'); Operator IS pozwal na sprawdzenie, czy dana wartość jest NULL lub nie jest: SELECT * FROM wypozyczenie WHERE data_zwrotu IS NULL; SELECT * FROM wypozyczenie WHERE data_zwrotu IS NOT NULL; Operatory i klauzulę WHERE można stosować w instrukcjach SELECT, UPDATE lub DELETE do wybierania zakresu rekordów na których dokonywane będą przewidziane operacje. Dzięki operatorom AND i OR można budować bardzo wyszukane warunki. Zapytania złożone i widoki. Przypomnijmy sobie zapytania złożone z wielu tabel, które przerabialiśmy podczas ostatnich zajęć. Utrwalmy te zapytania w formie widoków. SELECT k.imie, k.nazwisko, p.tytul,w.data_wypozyczenia, w.data_zwrotu FROM klient k, wypozyczenie w, plyta p 12
WHERE k.id_klienta = w.kto_wypozyczyl AND p.numer = w.co_wypozyczyl; SELECT k.imie, k.nazwisko, p.tytul,w.data_wypozyczenia, w.data_zwrotu FROM klient k LEFT JOIN wypozyczenie w ON k.id_klienta = w.kto_wypozyczyl LEFT JOIN plyta p ON p.numer = w.co_wypozyczyl; oraz SELECT k.imie, k.nazwisko, p.tytul,w.data_wypozyczenia, w.data_zwrotu FROM klient k JOIN wypozyczenie w ON k.id_klienta = w.kto_wypozyczyl RIGHT JOIN plyta p ON p.numer = w.co_wypozyczyl; Ale struktura naszej Bazy danych jest dalece bardziej rozbudowana niż spróbujmy zastem wyświetlić zdecydowanie więcej danych. Zanim jednak to uczynimy dodajmy kila rekordów do tablicy jest pracownikiem. Pracownicy w naszej wypożyczalni to Jan Kowalski przysługuje mu rabat 50 % i Adam Brzeski z rabatem 40 %. Zatem dodajmy odpowiednie wpisy do tabeli jest_pracownikiem: INSERT INTO jest_pracownikiem(rabat,id_klienta) VALUES((SELECT id_klienta from klient where imie='jan' AND nazwisko = 'Kowalski'),50); Podobnie zrób dla pana Adama Brzeskiego z rabatem 40%. Teraz przepisz zapytanie w taki sposób, żeby pokazało tabelę wynikową w postaci: imię nazwisko tytuł gatunek data_wypozyczenia data_zwrotu cena rabat Łatwo zauważyć, że w pojedynczym zapytaniu z wykorzystaniem klauzuli WHERE jako warunku łączącego tabele. W wyniku tak postawionego zapytania otrzymamy jedynie wiersze dla wszystkich wypożyczonych płyt które mają gatunek i są wypożyczone przez klienta będącego pracownikiem: SELECT k.imie, k.nazwisko, p.tytul, g.nazwa, w.data_wypozyczenia, w.data_zwrotu, p.cena, j.rabat FROM klient k, plyta p, gatunek g, wypozyczenie w, jest_pracownikiem j WHERE k.id_klienta = w.kto_wypozyczyl and p.numer = w.co_wypozyczyl and k.id_klienta = j.id_klienta and p.gatunek = g.id_gatunku; 13
Przyjrzyjmy się instrukcji warunkowej po klauzuli where: k.id_klienta = w.kto_wypozyczyl and (prawdziwe dla wszystkich wypożyczeń) p.numer = w.co_wypozyczyl and (prawdziwe dla wszystkich wypożyczeń) k.id_klienta = j.id_klienta and (prawdziwe tylko dla pracowników) p.gatunek = g.id_gatunku;(prawdziwe tylko dla płyt z wypełnionym gatunkiem) Widać zatem, że część zapytania z komentarzem na czerwono znacznie ograniczy ilość wyświetlanych wyników. Musimy zatem skorzystać z innej postaci połączenia tabel przy pomocy JOIN. Sprawdźmy zatem jak zadziała połączenie join dla tych 5 tabel: SELECT k.imie, k.nazwisko, p.tytul, g.nazwa, w.data_wypozyczenia, w.data_zwrotu, p.cena, j.rabat FROM jest_pracownikiem j JOIN klient k ON k.id_klienta = j.id_klienta JOIN wypozyczenie w ON k.id_klienta = w.kto_wypozyczyl JOIN plyta p ON p.numer = w.co_wypozyczyl JOIN gatunek g ON p.gatunek = g.id_gatunku; Jak to działa i jaka jest kolejność złączeń? Sprawdźmy to na następującym rysunku, gdzie odpowiednio j oznacza tabelę jest_pracownikiem, k tabelę klient, w tabelę wypozyczeie, p tabelę plyta i g tabelę gatunek: j k w j g Kolejne owale oznaczają tutaj kolejność łączeń. Jako pierwsze połączenie występuje tutaj połączenie klienta z jest_pracownikiem. Tabela jest_pracownikiem występuje po lewej stronie związku, ale powinniśmy w wyniku zapytania wyświetlić wszystkich klientów nawet jeśli nie są pracownikami. Chronimy zatem prawą stronę związku tabel klient i jest_pracownikiem. Sprawdźmy jak zadziała ta część zapytania: SELECT k.imie, k.nazwisko, j.rabat from jest_pracownikiem j JOIN klient k ON k.id_klienta = j.id_klienta; SELECT k.imie, k.nazwisko, j.rabat from jest_pracownikiem j RIGHT JOIN klient k ON k.id_klienta = j.id_klienta; Następną w kolejności złączeń tabelą jest wypożyczenie dołączmy ją do poprzedniego zapytania: 14
SELECT k.imie, k.nazwisko, j.rabat, w.data_wypozyczenia, w.data_zwrotu from jest_pracownikiem j RIGHT JOIN klient k ON k.id_klienta = j.id_klienta JOIN wypozyczenie w ON w.kto_wypozyczyl = k.id_klienta; Łatwo zauważyć, że taki JOIN zepsuje wynik RIGHT JOIN'a z poprzedniego zapytania. Ponieważ to co chcemy chronić jest po lewej stronie związku musimy skorzystać z LEFT JOIN w kolejnym złączeniu. Sprawdźmy to: SELECT k.imie, k.nazwisko, j.rabat, w.data_wypozyczenia, w.data_zwrotu from jest_pracownikiem j RIGHT JOIN klient k ON k.id_klienta = j.id_klienta LEFT JOIN wypozyczenie w ON w.kto_wypozyczyl = k.id_klienta; Podobnie dołączmy płytę i gatunek: SELECT k.imie, k.nazwisko, p.tytul, g.nazwa, w.data_wypozyczenia, w.data_zwrotu, p.cena, j.rabat FROM jest_pracownikiem j RIGHT JOIN klient k ON k.id_klienta = j.id_klienta LEFT JOIN wypozyczenie w ON k.id_klienta = w.kto_wypozyczyl LEFT JOIN plyta p ON p.numer = w.co_wypozyczyl LEFT JOIN gatunek g ON p.gatunek = g.id_gatunku; Utrwalmy podobne do powyższego zapytanie w formie widoku: CREATE VIEW raport1 AS SELECT k.imie ' ' k.nazwisko, p.tytul, g.nazwa, w.data_wypozyczenia, w.data_zwrotu, p.cena, j.rabat FROM jest_pracownikiem j RIGHT JOIN klient k ON k.id_klienta = j.id_klienta LEFT JOIN wypozyczenie w ON k.id_klienta = w.kto_wypozyczyl LEFT JOIN plyta p ON p.numer = w.co_wypozyczyl LEFT JOIN gatunek g ON p.gatunek = g.id_gatunku; Użyliśmy tutaj operatora łączenia ciągów znaków. Pozwala to na połączenie np. imienia i nazwiska klienta tak, aby znalazły się w jednej kolumnie. Dodatkowo wymuszamy zmianę nazwy wyświetlanej kolumny dyrektywą AS. Funkcje agregacji W poprzednim ćwiczeniu pokazana została funkcja, dzięki której można dokonać połączenia ciągu znaków. Istnieje szereg innych funkcji min matematycznych. Sprawdźmy działanie niektórych z 15
nich: SELECT 2+2; SELECT sin(1); SELECT log(numer) FROM plyta; SELECT sin(cena) FROM plyta; Ale istnieją także inne funkcje: SELECT count(*) FROM gatunek; SELECT count(imie) FROM klient; SELECT max(id_klienta) FROM klient; SELECT min(id_klienta) FROM klient; SELECT avg(id_klienta) FROM klient; Te funkcje w odróżnieniu od poprzednich zwracają pojedynczy wynik a operują na grupach krotek. Funkcje takie noszą miano funkcji agregacji, gdyż dokonują analizy na grupie krotek. Gdy wykonamy funkcję sin(id_klienta) to zwróci ona tyle wyników na ilu krotkach się ona wykonała, natomiast funkcje takie jak max(), min(), avg(), variance(), stdev() czy count() zwrócą pojedynczy wynik. Z tymi funkcjami związana jest dodatkowa klauzula zapytania SELECT : GROUP BY. GROUP BY powoduje przestawienie wyników w taki sposób, aby stanowiły one przegrupowaną tabelę względem kryterium. Na tak przegrupowanej tabeli możliwe jest wykonywanie funkcji agregacji i dalsza analiza wyników. Przykładowo chcemy dokonać statystycznego zestawienia imion występujących w tabeli klient: SELECT imię, count(imie) FROM klient GROUP BY imię; Z klauzulą GOUP BY związana jest klauzula HAVING, która działa tak samo jak WHERE dla normalnej wersji SELECT: SELECT imię, count(imie) FROM klient GROUP BY imię HAVING imię < 'J'; W ramach laboratorium pokazane zostały metody manipulowania danymi, operatory, zapytania złożone, funkcje agregacji, klauzula GROUP BY oraz możliwość utrwalania zapytań w formie widoków. Na następnych zajęciach wprowadzone zostaną szerzej funkcje i pokazane metody dalszej analizy danych oraz transakcje. Kopie zapasowe i odzyskiwanie danych W PostgreSQL możliwe jest wykonanie kopii zapasowej bazy danych. Można tego dokonać przy pomocy polecenia systemowego pg_dump nazwabazy. W wyniku działania tego polecenia na 16
ekranie terminala pojawi się kod SQL, który pozwoli na ponowne założenie bazy danych. Przykład: Opuść bazę danych (\q) pg_dump lab2_login Teraz przekierujmy wynik działania polecenia do pliku: pg_dump lab2_login > lab2_login_dump.sql możesz teraz podejrzeć zawartość tego pliku np. przy pomocy edytora mcedit: mcedit lab2_login_dump.sql W tym miejscu połącz się za swoją bazą ponownie i poćwicz usuwanie danych. Spróbuj skasować płyty, gatunki i wypożyczenia. Teraz możliwe będzie odtworzenie tej bazy danych przy pomocy polecenia psql: Usuńmy bazę danych: dropdb lab2_login stwórzmy ją na nowo (będzie pusta) : createdb lab2_login sprawdźmy, że jest pusta: psql lab2_login \d No relations found \i lab2_login_dump.sql \d SELECT * FROM klient; Widać, że nasza baza odzyskała postać i dane z pliku. 17