1. Utwórz bazę o nazwie test 2. Zaimportuj strukturę i dane z pliku baza_test.txt 3. Powyższy zbiór poleceo SQL utworzony w bazie tablice i wpiszę ich zawartości wg poniżeszego schematu: Pobieranie danych z pojedynczych tabel W tym odcinku po raz pierwszy spojrzymy na tabele jako na zbiory danych, poznamy język SQL i nauczymy się odczytywać, wybierać i sortować dane zapisane w pojedynczych tabelach. Wprowadzenie Podstawowe obiekty relacyjnych baz danych tabele i główny język programowania tych baz SQL wymagają krótkiego przedstawienia. Tabele jako zbiory danych
Podstawową cechą relacyjnych baz danych jest to, że przechowywane informacje są dostępne jako zbiór dwuwymiarowych tabel. Czyli niezależnie od tego, w jakiej postaci (z reguły jest to postać binarna) serwer baz danych przechowuje na dysku i w pamięci dane, użytkownicy widzą je w postaci tabelarycznej. Każda tabela musi mieć unikatową w skali schematu (w przypadku MySQL-a schemat i baza są tym samym) nazwę. Nazwę obiektu bazodanowego można poprzedzić nazwą schematu, w którym ten obiekt się znajduje, tak więc pełną nazwą tabeli customer będzie test.customer. Każda tabela składa się z kolumn i wierszy. Poszczególne kolumny muszą mieć określoną nazwę, a w każdej z nich można przechowywać dane określonego typu. Na przykład, w kolumnie fname tabeli customer można zapisać do 32 znaków, w kolumnie title przechowywane są ciągi znaków o długości dokładnie 4 znaków jeżeli tytuł jakiegoś klienta jest krótszy, zostanie uzupełniony spacjami. Natomiast poszczególne wiersze zawierają dane opisujące dany obiekt. Na przykład, jeden wiersz tabeli customer przechowuje imię, nazwisko, tytuł, adres, numer telefonu i identyfikator klienta. Tabele w relacyjnej bazie danych mają następujące właściwości: Nazwa kolumn musi być unikatowa w skali tabeli próba utworzenia tabeli z kilkoma tak samo nazywającymi się kolumnami zakończy się błędem. Kolejność wierszy jest nieokreślona i nieistotna to, że informacje o kliencie Jenny Stones znajdują się w pierwszym, a nie, na przykład, trzecim wierszu, nie ma żadnego znaczenia. Kolejność kolumn jest nieokreślona, ale ma wpływ na sposób prezentowania danych gdyby dane o numerze telefonu znajdowały się w pierwszej, a nie ostatniej kolumnie, dalej byłyby to te same dane, ale zapytania mogłyby uwzględnić zmienioną kolejność kolumn. Wiersze w tabeli muszą być różne na przykład, gdybyśmy chcieli wprowadzić drugi raz informację o tym samym kliencie, musielibyśmy dodać kolejny wiersz. W matematycznym modelu relacyjnych baz danych przyjmuje się, że dwa identyczne elementy zbioru są tak naprawdę jednym elementem, natomiast w tabeli identyczne dane przechowywane są w różnych wierszach. Pobieranie danych Informacje przechowywane w bazach danych mogą zostać pobrane za pomocą instrukcji języka SQL SELECT. Instrukcja SELECT (zapytanie) określa, jakie dane mają zostać zwrócone w wyniku jej wykonania, natomiast to, w jaki sposób instrukcja zostanie wykonana, zależy od serwera baz danych. Język SQL jest językiem strukturalnym, a nie proceduralnym. W tym języku nie określamy sposobu wykonania zadania (tak jak np. w C), ale jego wynik (a właściwie wymagane do
jego otrzymania operacje na pewnych strukturach zbiorach). Z założenia instrukcje języka SQL przypominają potoczny język angielski. Na przykład, żeby odczytać nazwę i cenę produktu, nie napiszemy programu za pomocą jakiegoś algorytmu wyszukiwania znajdującego odpowiednie informacje, ale po prostu powiemy: Odczytaj nazwę i cenę produktu o podanym identyfikatorze. Instrukcja SELECT służy do pobierania danych z bazy. Instrukcja musi zawierać (z wyjątkiem polecenia SELECT odwołującego się wyłącznie do stałych, zmiennych lub wyrażeń arytmetycznych) co najmniej jedną klauzulę: za pomocą polecenia SELECT określamy interesujące nas kolumny (dokonujemy operacji selekcji pionowej, projekcji), za pomocą klauzuli FROM wskazujemy tabelę, z której pobieramy dane. Z reguły ogranicza się również, za pomocą klauzuli WHERE, liczbę zwracanych wierszy do rekordów spełniających określone kryteria (operacja selekcji poziomej, selekcji). Odczytujemy wszystkie dane z tabeli Najprostszy przykład użycia instrukcji SELECT to odczytanie całej zawartości wskazanej tabeli. W SQL-u możemy posługiwać się kilkoma znakami specjalnymi, jednym z nich jest * oznaczająca wszystko. Czyli żeby odczytać wszystko z tabeli customer, należy wykonać instrukcję: SELECT * FROM customer; albo: SELECT * FROM test.customer; Wszystkie instrukcje SQL należy wykonywać w wyniku łączenia się jako administrator testowej bazy za pomocą programu MySQL Query Browser albo tekstowego programu mysql. W drugim przypadku po połączeniu z serwerem trzeba określić bazę danych przez wpisanie USE test;. Kolejność kolumn wyniku zawsze odpowiada kolejności kolumn tabeli. Natomiast nie
należy zakładać, że ponowne wykonanie tej samej instrukcji zwróci wiersze w tej samej kolejności. Porządkowanie danych Instrukcja SELECT zwraca wiersze w tej kolejności, w jakiej dane są przechowywane w tabeli. Z reguły jest to kolejność, w jakiej były dopisywane następne wiersze z danymi. Do zmiany kolejności, w jakiej zwracane będą wyniki zapytania służy klauzula ORDER BY. ORDER BY (uporządkuj według) jest opcjonalnym składnikiem instrukcji SELECT. Jeżeli jednak ta klauzula wystąpi, musi być ostatnią. Kolejność klauzul instrukcji SELECT nie jest dowolna. Obowiązkowym parametrem klauzuli ORDER BY jest wyrażenie lub nazwa kolumny, według wartości których należy posortować dane wynikowe. Wykonanie poniższej instrukcji spowoduje wyświetlenie opisów towarów i cen ich zakupu uszeregowanych według cen zakupu (listing 3.14). Listing 3.14. Posortowana lista towarów SELECT description, cost_price FROM item ORDER BY cost_price; +-- description cost_price +-- SQL Server 2005 NULL Carrier Bag 0.01 Toothbrush 0.75 Linux CD 1.99 Tissues 2.11 Roman Coin 2.34 Rubik Cube 7.45 Picture Frame 7.54 Fan Small 9.23 Fan Large 13.36 Wood Puzzle 15.23 Speakers 19.73 +-- Domyślnie dane szeregowane są w porządku rosnącym, czyli od wartości najmniejszych do największych w przypadku danych liczbowych, od najwcześniejszych do najpóźniejszych w przypadku dat oraz w porządku alfabetycznym w przypadku ciągów znakowych. Aby odwrócić kolejność sortowania, należy bezpośrednio po nazwie kolumny użyć słowa kluczowego DESC (ang. Descending) (listing 3.15). Listing 3.15. Lista towarów posortowana od najdroższego do najtańszego SELECT description, cost_price FROM item ORDER BY cost_price DESC; +-- description cost_price
+-- Speakers 19.73 Wood Puzzle 15.23 Fan Large 13.36 Fan Small 9.23 Picture Frame 7.54 Rubik Cube 7.45 Roman Coin 2.34 Tissues 2.11 Linux CD 1.99 Toothbrush 0.75 Carrier Bag 0.01 SQL Server 2005 NULL +-- Poszczególne klauzule instrukcji SELECT są od siebie niezależne. To znaczy, że w klauzuli ORDER BY możemy użyć wyrażenia lub nazwy kolumny, która nie występuje w klauzuli SELECT (listing 3.16). Listing 3.16. Lista nazw towarów posortowana według cen ich zakupu SELECT description FROM item ORDER BY cost_price; description SQL Server 2005 Carrier Bag Toothbrush Linux CD Tissues Roman Coin Rubik Cube Picture Frame Fan Small Fan Large Wood Puzzle Speakers Oczywiście możemy sortować dane według więcej niż jednego kryterium. Instrukcja z listingu 3.17 zawiera alfabetycznie uporządkowaną listę adresów klientów najpierw dane sortowane są według nazw miast, a następnie według nazw ulic. Listing 3.17. Jeżeli kilku klientów mieszka w tym samym mieście, o ich kolejności na liście zadecyduje nazwa i numer ulicy SELECT town, addressline FROM customer ORDER BY town, addressline; -+ town addressline -+ Bingham 34 Holly Way Bingham 34 Holly Way Bingham 54 Vale Rise
Hightown 27 Rowan Avenue Histon 36 Queen Street Lowtown 52 The Willows Milltown 4 The Square Nicetown 4 The Street Nicetown 5 Pasture Lane Oahenham 7 Shady Lane Oxbridge 73 Margeritta Way Tibsville 86 Dysart Street Welltown 2 Beamer Street Winersby 42 Thached way Yuleville The Barn -+ Wybieranie wierszy Instrukcje SELECT w tej postaci, której używaliśmy do tej pory, zwracały wszystkie wiersze z danej tabeli. Do ograniczenia (wybrania) wierszy w wyniku należy użyć klauzuli WHERE. Ta klauzula odpowiada teoriomnogościowemu operatorowi selekcji, czyli wybierania wierszy. Operacja ta najczęściej polega na wyborze, na podstawie pewnych kryteriów, grupy wierszy z tabeli. Serwer baz danych dla każdego wiersza sprawdzi, czy spełnia on kryteria wyboru, i jeżeli tak zostanie on dodany do wyniku zapytania. Klauzula WHERE, o ile została użyta, musi wystąpić bezpośrednio po klauzuli FROM. Kryterium wyboru może zostać sformułowane za pomocą typowych operatorów porównania lub operatorów charakterystycznych dla języka SQL. Typowe operatory porównania Operatory logiczne porównują, czy dany warunek jest spełniony, czyli czy w wyniku porównania argumentów otrzymamy wartość logiczną True (Prawda). Na przykład, wszystkie poniższe warunki są prawdziwe: 1<4, 2=2, 5>=5, 'mama'='mama' itd. Zwróćmy uwagę, że ciągi znaków muszą być umieszczone wewnątrz apostrofów. Domyślnie MySQL przy porównywaniu ciągów znaków nie rozróżnia wielkich i małych liter (listing 3.18). Listing 3.18. Wynikiem porównania wyrazów kot i KOT jest wartość prawda SELECT 'kot' = 'KOT'; 'kot' = 'KOT' 1 Na przykład, aby wybrać tylko te towary, których cena zakupu nie przekracza 5 zł, należy wykonać instrukcję z listingu 3.19. Listing 3.19. Prosty test logiczny wybierający towary o określonej cenie zakupu SELECT * FROM item WHERE cost_price <=5; -+ item_id description cost_price sell_price -+ 3 Linux CD 1.99 2.49
4 Tissues 2.11 3.99 8 Toothbrush 0.75 1.45 9 Roman Coin 2.34 2.45 10 Carrier Bag 0.01 0.00 -+ Zwróć uwagę, że w wyniku nie znalazł się towar o nieokreślonej nazwie. Pamiętaj że wartość Null ma specjalne znaczenie i nie można jej sensownie używać z operatorami mniejszy czy równy. Aby ograniczyć liczbę informacji o interesujących nas towarach (towarach kupionych za nie więcej niż 5 zł) do ich nazwy i obu cen (zakupu i sprzedaży), należy połączyć w jednej instrukcji operacje projekcji i selekcji. Dodatkowo, wykorzystując klauzulę ORDER BY, możemy posortować wynik zapytania w kolejności od towarów kupionych najtaniej do kupionych najdrożej. Zmodyfikowane polecenie SELECT powinno wyglądać następująco (listing 3.20). Listing 3.20. Przykład wykorzystania poznanych do tej pory wiadomości o instrukcji SELECT SELECT description, cost_price, sell_price FROM item WHERE cost_price <=5 ORDER BY cost_price; +-+ description cost_price sell_price +-+ Carrier Bag 0.01 0.00 Toothbrush 0.75 1.45 Linux CD 1.99 2.49 Tissues 2.11 3.99 Roman Coin 2.34 2.45 +-+ Język SQL pozwala również na użycie nazw kolumn po obu stronach operatora porównania. Na przykład, aby wyświetlić informacje o tych towarach, które sprzedajemy poniżej ceny zakupu, napiszemy (listing 3.21). Listing 3.21. Porównanie danych odczytanych z tabeli. Serwer baz danych sprawdzi ten warunek dla każdego wiersza tabeli SELECT * FROM item WHERE cost_price > sell_price; -+ item_id description cost_price sell_price -+ 10 Carrier Bag 0.01 0.00 -+ Dopuszcza również tworzenie bardziej skomplikowanych, wykorzystujących operatory AND (logiczne i, koniunkcja), OR (logiczne lub, alternatywa) oraz NOT (logiczne nie, negacja), warunków logicznych. Wynik operacji porównania obliczany jest na podstawie tabel
prawdziwości danego operatora. Tabele prawdziwości dla operatorów NOT, AND i OR przedstawione są na rysunku 3.1. Wartość 1 odpowiada prawdzie, 0 fałszowi. Rysunek 3.1. Tabele prawdziwości operatorów logicznych Listing 3.22 pokazuje przykład użycia złożonego warunku logicznego w wyniku zapytania znajdą się tylko te zamówienia, które zostały złożone przed końcem czerwca 2000 roku przez klienta o identyfikatorze 8. Listing 3.22. Koniunkcja ogranicza wiersze wyniku SELECT * FROM orderinfo WHERE customer_id=8 AND date_placed <'2000-06-30'; -+--+ orderinfo_id customer_id date_placed date_shipped shipping -+--+ 2 8 2000-06-23 2000-06-23 0.00 -+--+ Specjalne znaczenie wśród operatorów logicznych ma operator negacji. W przeciwieństwie do pozostałych operatorów logicznych, jest operatorem jednoargumentowym, to znaczy, że do obliczenia wyniku wystarczy podać jeden argument. Jak wynika z tabeli prawdziwości, wynik operacji NOT a jest prawdą wtedy i tylko wtedy, gdy argument a był fałszywy. Operator negacji służy do zaprzeczania warunkom podanym w klauzuli WHERE (listing 3.23). Listing 3.23. Zapytanie zwracające informacje o mężczyznach spoza Lowtown SELECT title, fname, lname, town FROM customer WHERE title = 'Mr' AND town!='lowtown'; +- title fname lname town +- Mr Adrian Matthew Yuleville Mr Simon Cozens Oahenham Mr Neil Matthew Nicetown Mr Richard Stones Bingham Mr Mike Howard Tibsville Mr Dave Jones Bingham Mr Richard Neill Winersby Mr Bill Neill Welltown Mr David Hudson Milltown +-
Oczywiście, w klauzuli WHERE, tak jak w klauzulach SELECT czy ORDER BY, możemy używać wyrażeń. W efekcie możemy łatwo wybrać na przykład informacje o tych towarach, które sprzedajemy z ponad 50% marżą (listing 3.24). Listing 3.24. Przykład wykorzystania wyrażenia w klauzuli WHERE SELECT * FROM item WHERE sell_price>=1.5*cost_price; -+ item_id description cost_price sell_price -+ 2 Rubik Cube 7.45 11.49 4 Tissues 2.11 3.99 6 Fan Small 9.23 15.75 8 Toothbrush 0.75 1.45 -+ Pobieranie danych z wielu tabel W tym odcinku nauczymy się odczytywać dane zapisane w różnych tabelach, poznamy różnice pomiędzy złączeniem wewnętrznym a złączeniami zewnętrznymi i dowiemy się, jak przeprowadzać na tabelach operacje na zbiorach, znane z lekcji matematyki. Wprowadzenie Cechą charakterystyczną relacyjnych baz danych jest przechowywanie informacji podzielonych między wiele tabel. W wielu wypadkach, w trakcie wyszukiwania informacji w bazie danych, okazuje się, że potrzebne dane przechowywane są w kilku tabelach. Aby sensownie połączyć w jednym zapytaniu dane z wielu tabel, wymagane jest ich złączenie (ang. Join). O złączeniu tabel możemy myśleć jako o następującej operacji (w rzeczywistości serwery baz danych optymalizują łączenie tabel): Pierwszym etapem jest obliczenie wyniku iloczynu kartezjańskiego łączonych tabel kombinacji wszystkich wierszy z pierwszej tabeli z wszystkimi wierszami z drugiej tabeli. Jeśli każda tabela zawiera tylko jeden wiersz, to wynik iloczynu kartezjańskiego też będzie miał jeden wiersz. W przypadku tabel o 5 wierszach, wynik iloczynu kartezjańskiego wynosi 25 wierszy. Iloczyn kartezjański trzech tabel o, odpowiednio, 30, 100 i 10 wierszach daje w wyniku tabelę z 30 000 wierszy. Ten ogromny zbiór stanowi podstawę dla dalszego wykonywania zapytania. Przede wszystkim usuwane są z niego wiersze niespełniające warunku złączenia. Dzięki temu pozbywamy się ogromnej liczby powtórzonych i bezsensownych kombinacji danych. Kolejny krok polega na wykonaniu ograniczeń wynikających z klauzul WHERE i HAVING. Wszystkie wiersze, które nie spełniają określonych w nich warunków są odrzucane. Końcowym etapem jest wybranie z tabeli kolumn zawartych w klauzuli SELECT i wykonanie odpowiedniej projekcji. Z reguły łączy się tabele na podstawie wartości wspólnego atrybutu, na przykład wartości pary klucz podstawowy-klucz obcy. W takim przypadku musimy użyć jednoznacznego identyfikatora obiektu (kolumny). Ponieważ nazwy kolumn zawierających klucz podstawowy
i obcy najczęściej są takie same, musimy poprzedzać nazwy kolumn nazwami tabel. Możemy poprawić czytelność zapytania, stosując aliasy dla nazw tabel. Łącząc tabele, stosujemy się do następujących wskazówek: 1. Staramy się łączyć tabele za pomocą kolumn przechowujących parę kluczy podstawowy-obcy. 2. Do złączenia używamy całych kluczy podstawowych tabel. Jeżeli dla jakiejś tabeli zdefiniowano złożony (składający się z kilku atrybutów) klucz podstawowy, łącząc taką tabelę, odwołujemy się do całego klucza; 3. Łączymy obiekty za pomocą kolumn tego samego typu. 4. Poprzedzamy nazwy kolumn aliasem nazwy obiektu źródłowego, nawet jeżeli ich nazwy są unikatowe w ten sposób poprawimy czytelność zapytania. 5. Ograniczamy liczbę łączonych obiektów do niezbędnego minimum. Złączenie naturalne Wynikiem złączenia naturalnego jest zbiór wierszy łączonych tabel, dla których wartości kolumn określonych jako warunek złączenia są takie same. Ponieważ w relacyjnych bazach danych informacje są podzielone pomiędzy tabele zawierające dane o obiektach jednego typu, złączenie naturalne jest najczęściej wykorzystywanym (i domyślnym) złączeniem obiektów. W przykładowej bazie danych informacje o klientach, zamówieniach, towarach i stanach magazynowych przechowywane są w powiązanych ze sobą tabelach. Dlatego, żeby odczytać na przykład daty składania przez poszczególnych klientów zamówień, musimy odwołać się do dwóch tabel nazwę klienta odczytamy z tabeli customer, a datę złożenia zamówienia z tabeli orderinfo. Przy czym z reguły nie chodzi nam o uzyskanie poniższego wyniku (listing 4.1). Listing 4.1. Odwołując się do wielu tabel, powinniśmy określić warunek złączenia, inaczej wynik będzie zawierał wszystkie kombinacje wierszy wymienionych tabel (iloczyn kartezjański). W tym przypadku tabela customer liczy 16, a tabela orderinfo 5 wierszy SELECT lname, date_placed FROM customer, orderinfo; -+ lname date_placed -+ Stones 2000-03-13 Stones 2000-06-23 Stones 2000-09-02 Stones 2000-09-03 Stones 2000-07-21 Stones 2000-03-13 Stones 2000-06-23 Stones 2000-09-02 Stones 2000-09-03 Stones 2000-07-21 Matthew 2000-03-13 Wolski 2000-07-21 -+ 80 rows in set
Poprawnie napisana instrukcja powinna zwrócić nam tylko daty zamówień złożonych przez danego klienta. Żeby to osiągnąć, musimy określić warunek złączenia, czyli poinformować serwer baz danych, co łączy zapisane w obu tabelach dane. W tym przypadku jest to identyfikator klienta zwróć uwagę, że kolumna customer_id występuje w obu tabelach, czyli na podstawie tego identyfikatora jesteśmy w stanie sensownie połączyć informacje o klientach z informacjami o zamówieniach (listing 4.2). Listing 4.2. Poprawne zapytanie zwracające nazwiska klientów i daty złożenia przez nich zamówień SELECT lname, date_placed FROM customer INNER JOIN orderinfo ON customer.customer_id = orderinfo.customer_id; -+ lname date_placed -+ Matthew 2000-03-13 Stones 2000-06-23 Hudson 2000-09-02 Hendy 2000-09-03 Stones 2000-07-21 -+ Złączenie naturalne pozwoli nam również odczytać kody poszczególnych towarów. Jednak tym razem użyjemy nieco innej składni zamiast dość rozwlekłej klauzuli ON, skorzystamy z jej bardziej zwięzłego odpowiednika klauzuli USING (listing 4.3). Listing 4.3. Złączenie naturalne za pomocą klauzuli USING SELECT description, barcode_ean FROM barcode INNER JOIN item USING (item_id); + description barcode_ean + Wood Puzzle 6241527836173 Rubik Cube 6241574635234 Linux CD 6241527746363 Linux CD 6264537836173 Tissues 7465743843764 Picture Frame 3453458677628 Fan Small 6434564564544 Fan Large 8476736836876 Toothbrush 6241234586487 Toothbrush 9473625532534 Toothbrush 9473627464543 Roman Coin 4587263646878 Speakers 2239872376872 Speakers 9879879837489 +
Klauzule ON i USING są różnymi sposobami na podanie warunku złączenia, czyli wskazania wspólnych kolumn łączonych tabel. Złączenia zewnętrzne Złączenie naturalne eliminuje z wyniku niepasujące (niespełniające warunku złączenia) wiersze. To dobrze, bo w innym przypadku otrzymalibyśmy zawierający mnóstwo powtórzeń i niepotrzebnych danych iloczyn kartezjański. Ale z drugiej strony, ten sam warunek złączenia usunął z wyniku rekordy niemające odpowiedników w łączonej tabeli. Czyli wynik poniższej instrukcji wcale nie musi zawierać danych wszystkich naszych klientów (listing 4.6). Listing 4.6. W wyniku złączenia naturalnego nie znajdziemy nazwisk klientów, którzy nie złożyli przynajmniej jednego zamówienia SELECT lname, date_placed FROM customer INNER JOIN orderinfo ON customer.customer_id = orderinfo.customer_id; -+ lname date_placed -+ Matthew 2000-03-13 Stones 2000-06-23 Hudson 2000-09-02 Hendy 2000-09-03 Stones 2000-07-21 -+ Czasami chcielibyśmy uzyskać komplet danych z jednej tabeli, nawet jeżeli nie są one powiązane z danymi w innych tabelach. Umożliwia nam to złączenie zewnętrzne. Wynikiem lewo- lub prawostronnego złączenia zewnętrznego jest zbiór wierszy łączonych tabel, dla których wartości kolumn określonych jako warunek złączenia są takie same; zbiór ten uzupełniony jest pozostałymi wierszami z lewej lub prawej łączonej tabeli. Nieistniejące wartości reprezentowane są w wyniku złączenia przez wartość NULL (listing 4.7). Listing 4.7. Kompletna, ale zawierająca powtórzenia lista nazwisk klientów i dat złożenia przez nich zamówień SELECT lname, date_placed FROM customer LEFT OUTER JOIN orderinfo ON customer.customer_id = orderinfo.customer_id; -+ lname date_placed -+ Stones Stones Matthew 2000-03-13 Matthew Cozens Matthew Stones Stones 2000-06-23
Stones 2000-07-21 Hickman Howard Jones Neill Hendy 2000-09-03 Neill Hudson 2000-09-02 Wolski -+ Złączenia zewnętrzne wykorzystywane są do wyświetlania kompletnych informacji o wszystkich obiektach danego typu, nawet jeżeli nie istnieją powiązane z nimi obiekty innego typu. Wykorzystując wiadomości z poprzedniego odcinka kursu, możemy uporządkować tę listę (listing 4.8). Listing 4.8. Finalna wersja instrukcji zwracającej nazwiska wszystkich klientów i daty składania przez nich zamówień SELECT DISTINCT lname, date_placed FROM customer LEFT JOIN orderinfo USING (customer_id) ORDER BY lname; -+ lname date_placed -+ Cozens Hendy 2000-09-03 Hickman Howard Hudson 2000-09-02 Jones Matthew Matthew 2000-03-13 Neill Stones Stones 2000-07-21 Stones 2000-06-23 Wolski -+ Złączenie krzyżowe Wynikiem złączenia krzyżowego jest iloczyn kartezjański łączonych obiektów. W przeciwieństwie do innych typów złączeń, w tym wypadku łączone tabele nie muszą mieć wspólnych kolumn. Złączenia tego typu są rzadko stosowane w znormalizowanych bazach danych i służą raczej do generowania danych testowych niż do wybierania danych (listing 4.9). Listing 4.9. Wynikiem złączenia krzyżowego jest iloczyn kartezjański SELECT * FROM barcode CROSS JOIN stock; -+
barcode_ean item_id item_id quantity -+ 2239872376872 11 1 12 2239872376872 11 2 2 2239872376872 11 4 8 2239872376872 11 5 3... 9879879837489 11 10 1 -+ 98 rows in set (0.00 sec) Złączenie nierównościowe Powiązania tabel wykorzystujące dowolny, inny niż równość, operator nazywane są nierównościowymi (ang. Non-equi join). Tego typu złączenia z reguły są używane przy łączeniu tabeli z nią samą albo jako dodatkowe złączenie, obok złączenia równościowego. Samodzielne złączenie nierównościowe zwraca mało intuicyjne wyniki (listing 4.10). Listing 4.10. Przykład złączenia nierównościowego SELECT lname, date_placed FROM customer INNER JOIN orderinfo ON customer.customer_id > orderinfo.customer_id WHERE date_placed BETWEEN '2000=03-01' AND '2000-03-30'; -+ lname date_placed -+ Matthew 2000-03-13 Cozens 2000-03-13 Matthew 2000-03-13 Stones 2000-03-13 Stones 2000-03-13 Hickman 2000-03-13 Howard 2000-03-13 Jones 2000-03-13 Neill 2000-03-13 Hendy 2000-03-13 Neill 2000-03-13 Hudson 2000-03-13 Wolski 2000-03-13 -+ Zwróć uwagę na warunek w klauzuli WHERE. Żeby wybrać zamówienia z marca 2000 roku, należało określić przedział czasu Złączenie tabeli z nią samą Złączenie tabeli z nią samą stosujemy, kiedy chcemy wybrać rekordy z tabeli na podstawie wspólnych wartości atrybutów rekordów tej samej tabeli.
Złączenie tabeli z nią samą jest jedną z technik języka SQL, odpowiadającą użyciu zmiennych w proceduralnych językach programowania. Przy łączeniu w ten sposób, należy pamiętać o następujących zasadach: 1. Trzeba utworzyć różne aliasy dla łączonej tabeli i w ramach zapytania konsekwentnie odwoływać się do aliasów, a nie do nazwy tabeli. 2. Każdy rekord, w którym wartości atrybutu złączenia będą sobie równe, zostanie dodany do wyniku złączenia, co spowoduje powstanie duplikatów rekordów. Złączenia tabeli z samą sobą są często wykorzystywane do rekurencyjnego odczytania danych, na przykład informacji o podwładnych (podwładny osoby X może być przełożonym osoby Y, która z kolei może być przełożonym osoby Z). W testowej bazie danych nie ma zapisanych takich zależności. Przykład z listingu 4.11. jest czysto szkoleniowy. Listing 4.11. Złączenie tabeli z nią samą. Zwróć uwagę na liczbę powtórzonych wierszy SELECT l.customer_id, r.customer_id, l.lname, r.lname FROM customer l INNER JOIN customer r ON l.customer_id = r.customer_id; +-+-+ customer_id customer_id lname lname +-+-+ 1 1 Stones Stones 2 2 Stones Stones 3 3 Matthew Matthew 4 4 Matthew Matthew 5 5 Cozens Cozens 6 6 Matthew Matthew 7 7 Stones Stones 8 8 Stones Stones 9 9 Hickman Hickman 10 10 Howard Howard 11 11 Jones Jones 12 12 Neill Neill 13 13 Hendy Hendy 14 14 Neill Neill 15 15 Hudson Hudson 16 16 Wolski Wolski +-+-+ Jak wyeliminować te powtórzenia? Na pewno nie pomoże w tym słowo kluczowe DISTINCT przecież każdy wiersz zawiera niepowtarzalną kombinację danych. Rozwiązanie polega na dodaniu niesymetrycznego warunku (np. WHERE l.lname > r.lname), ale MySQL przy łączeniu tabeli z nią samą nie rozróżnia kolumn pochodzących z lewostronnie i prawostronnie złączonej tabeli, traktując je (niezgodnie ze standardem języka) jako te same. W efekcie dodanie takiego warunku wyeliminuje wszystkie wiersze. Złączenie wyników (operator UNION) Skoro tabele są specjalnymi zbiorami, to, tak jak zbiory, można je dodawać, odejmować i wyznaczać ich część wspólną. W wersji 5. MySQL obsługuje tylko jeden operator teoriomnogościowy sumę.
Do zsumowania wierszy z dowolnej liczby tabel służy operator UNION. Załóżmy, że A i B są zbiorami wtedy suma zbiorów A i B składa się z elementów obu zbiorów: AEB={x: xîa lub xîb} (rysunek 4.1). Rysunek 4.1. Na sumę dwóch tabel składają się wszystkie wiersze jednej tabeli i wszystkie wiersze drugiej tabeli Za pomocą operatora UNION możemy dodać wyniki poszczególnych zapytań (czyli zwiększyć liczbę wierszy wyniku. Złączenia JOIN zwiększały liczbę kolumn, złączenie UNION zwiększa liczbę wierszy). Łączone wyniki muszą składać się z takiej samej liczby kolumn, a poszczególne kolumny muszą być tego samego typu, poza tym konieczne jest, aby występowały one w tej samej kolejności w obu wynikach (listing 4.12). Listing 4.12. Złączenie wyników dwóch prostych zapytań SELECT fname FROM customer UNION SELECT description FROM item; fname Jenny Andrew Alex Adrian Simon Neil Richard Ann Christine Mike Dave Laura Bill David Wood Puzzle Rubik Cube Linux CD Tissues Picture Frame Fan Small
Fan Large Toothbrush Roman Coin Carrier Bag Speakers SQL Server 2005 Operatory teoriomnogościowe (takie jak UNION) automatycznie eliminują z wyniku powtarzające się wiersze, co odpowiada użyciu opcji DISTINCT w klauzuli SELECT. Jeżeli chcemy otrzymać listę zawierającą wszystkie wiersze z łączonych tabel, należy użyć słowa kluczowego ALL (listing 4.13). Listing 4.13. Przykład pokazujący domyślne usuwanie duplikatów z wyników złączenia UNION SELECT item_id FROM item UNION SELECT item_id FROM stock; item_id 1 2 3 4 5 6 7 8 9 10 11 12 SELECT item_id FROM item UNION ALL SELECT item_id FROM stock; item_id 1 2 3 4 5 6 7 8 9 10 11 12 1 2 4 5 7 8 10
Wybrane funkcje serwera MySQL Tak jak we wszystkich innych językach programowania, w języku SQL funkcje stanowią potężne narzędzie w pracy programisty zamiast samodzielnie pisać skomplikowane programy, wystarczy wywołać odpowiednią funkcję. W tym odcinku nauczymy się wywoływać funkcje systemowe i poznamy niektóre funkcje serwera MySQL. Wprowadzenie Wbudowane funkcje serwerów baz danych można podzielić na trzy kategorie: 1. Funkcje skalarne, które zwracają pojedynczą wartość obliczoną na podstawie zera lub większej liczby prostych argumentów. 2. Funkcje grupujące, które zwracają pojedynczą wartość dla zbioru argumentów wywołania. 3. Funkcje typu RowSet, zwracające dane w postaci tabelarycznej, do których odwołujemy się tak jak do tabel. Na podstawie typu parametrów wywołania funkcje skalarne można podzielić na: 1. Funkcje tekstowe operujące na ciągach znaków. 2. Funkcje liczbowe operujące na liczbach. 3. Funkcje daty i czasu operujące na danych typu data/godzina. 4. Funkcje konwersji służące do zmiany typu danych. Na specjalne wyróżnienie zasługują funkcje kryptologiczne, które pozwalają zaszyfrować, odszyfrować, podpisać i sprawdzić autentyczność wiadomości. W języku SQL funkcje można zagnieżdżać do dowolnego poziomu. Funkcje najbardziej wewnętrzne obliczane są w pierwszej kolejności, a na podstawie ich wyników obliczane są funkcje zewnętrzne. Funkcje tekstowe Argumentem funkcji tekstowych są ciągi znaków (dane typów char, varchar lub text). Typ danych zwracanych przez funkcje tekstowe jest podstawą do ich dalszego podziału: wyróżniamy funkcje tekstowe zwracające wartość znakową i funkcje tekstowe zwracające liczbę. Funkcje tekstowe zawierajace tekst CONCAT()
Funkcja łączy (konkatenuje) przekazane jako parametr i oddzielone przecinakami ciągi znaków. Używaliśmy już tej funkcji, a w następnych listingach użyjemy jej jeszcze kilka razy. LOWER() Wynikiem działania funkcji LOWER() jest ciąg znaków podany jako argument, ale składający się wyłącznie z małych liter. Za pomocą tej funkcji wszystkie wielkie litery argumentu zostaną zamienione na małe. Na przykład, aby wyświetlić nazwiska wszystkich współpracowników za pomocą małych liter, napiszemy (listing 5.1). Listing 5.1. Funkcji skalarnych można używać m.in. w klauzulach SELECT, WHERE i ORDER BY SELECT title, LOWER(title) FROM customer WHERE customer_id<3; +- title lower(title) +- Miss miss Mr mr +- UPPER() Wynikiem działania funkcji UPPER() jest ciąg znaków podany jako argument, ale składający się wyłącznie z wielkich liter. Za pomocą tej funkcji wszystkie małe litery argumentu zostaną zamienione na wielkie. Na przykład, aby uszeregować nazwy wszystkich towarów alfabetycznie według ich nazw, bez względu na wielkość użytych w nazwie liter, napiszemy (listing 5.2). Listing 5.2. Poszczególne klauzule instrukcji są od siebie niezależne na przykład, dane mogą być sortowane według wyrażenia niewymienionego w klauzuli SELECT SELECT * FROM item ORDER BY UPPER(description); --+ item_id description cost_price sell_price --+ 10 Carrier Bag 0.01 0.00 7 Fan Large 13.36 19.95 6 Fan Small 9.23 15.75 3 Linux CD 1.99 2.49 5 Picture Frame 7.54 9.95 9 Roman Coin 2.34 2.45 2 Rubik Cube 7.45 11.49 11 Speakers 19.73 25.32 12 SQL Server 2005 NULL NULL 4 Tissues 2.11 3.99 8 Toothbrush 0.75 1.45 1 Wood Puzzle 15.23 21.95 --+ Funkcje liczbowe ROUND()
Działanie funkcji ROUND() polega na zaokrągleniu liczby do określonej liczby cyfr po przecinku. Pierwszy parametr jest liczbą do zaokrąglenia, drugi wskazuje, do ilu pozycji chcemy zaokrąglić. Ujemna liczba powoduje zaokrąglenie liczby z lewej strony przecinka; 0 spowoduje zaokrąglenie do najbliższej liczby całkowitej. Jeżeli drugi parametr nie jest podany, SZBD przyjmuje domyślnie jego wartość jako równą 0 (listing 5.12). Listing 5.12. Przykład użycia funkcji ROUND() SELECT Round(3.1415926535897,4), Round(3.1415926535897,0), Round(3.1415926535897); -- Round(3.1415926535897,4) Round(3.1415926535897,0) Round(3.1415926535897) -- 3.1416 3 3 -- TRUNCATE() Funkcja TRUNCATE() powoduje obcięcie liczby do określonej liczby cyfr po przecinku. Pierwszy parametr jest liczbą do obcięcia, drugi wskazuje, do ilu pozycji chcemy liczbę skrócić. Ujemna liczba powoduje dodanie określonej liczby zer z lewej strony przecinka. Jeżeli drugi parametr nie jest podany, MySQL przyjmuje domyślnie jego wartość jako równą 0 (listing 5.13). Listing 5.13. Obcięcie miejsc po przecinku i zaokrąglenie do iluś miejsc po przecinku to dwie operacje SELECT Truncate(3.1415926535897,4); Truncate(3.1415926535897,4) 3.1415 ABS() Wynikiem działania funkcji ABS() jest wartość bezwzględna liczby (liczba bez znaku). Jako parametr podaje się liczbę, której wartość bezwzględną należy obliczyć (listing 5.14). Listing 5.14. Funkcja ABS() użyta do sprawdzenia, czy dana liczba jest dodatnia SELECT * FROM item WHERE sell_price!= ABS(sell_price); Empty set CEILING(), FLOOR() Za pomocą funkcji CEILING() zwrócona zostanie najmniejsza liczba całkowita równa liczbie podanej jako argument funkcji lub większa. Funkcja FLOOR() zwraca największą liczbę całkowitą równą liczbie podanej jako argument funkcji lub mniejszą (listing 5.15). Listing 5.15. Zwróć uwagę na wynik wywołania funkcji z argumentem Null SELECT sell_price, CEILING(sell_price), FLOOR(sell_price)