Funkcje w MySQL; sortowania ciÄ…g dalszy

PrzejdÄ…my teraz dalej. Bardzo możliwe, że kiedyÅ› bÄ™dziesz chciaÅ‚ posortować wyniki wzglÄ™dem dÅ‚ugoÅ›ci tekstu w jakimÅ› polu. Tutaj także możesz zrezygnować z innych jÄ™zyków na rzecz SQL'a, gdyż posiada on (jak każdy porzÄ…dny jÄ™zyk) funkcje. PozwalajÄ… nam one na operowanie i przetwarzanie danych jeszcze na poziomie ich pobierania! Stosować je można w każdym wyrażeniu na takich samych zasadach, jak w innych jÄ™zykach. SkÅ‚adnia wywoÅ‚ania jest podobna do tej z jÄ™zyka C - funkcje bez parametrów wywoÅ‚ujemy jako FUNKCJA(), natomiast z parametrami - FUNKCJA(jakis_parametr, jakis_parametr).

Funkcjami możemy posÅ‚użyć siÄ™ przy sortowaniu tekstu. Kiedy chcesz posortować rekordy wedÅ‚ug dÅ‚ugoÅ›ci ciÄ…gu w pewnym polu, wykorzystasz funkcjÄ™ LENGTH(tekst), która zwróci dÅ‚ugość podanego tekstu. Oto przykÅ‚ad, tym razem na tabeli length_sort.

CREATE TABLE length_sort (
   id smallint(10) NOT NULL AUTO_INCREMENT,
   string varchar(128),
   PRIMARY KEY (id)
);
 
INSERT INTO length_sort VALUES (1, 'to jest tekst');
INSERT INTO length_sort VALUES (2, 'tekst');
INSERT INTO length_sort VALUES (3, 'Ten fragment dzieła "Pan Tadeusz" poruszył mnie do łez');
INSERT INTO length_sort VALUES (4, 'a');
INSERT INTO length_sort VALUES (5, 'dwa miliony trzysta dwadzieścia trzy tysiące siedemset pięćdziesiąt dziewięć');

Tabela zostaÅ‚a wypeÅ‚niona różnymi tekstami. Każdy z nich ma różnÄ… dÅ‚ugość. Do realizacji naszego zamiaru użyjemy zapytania:

SELECT id FROM length_sort ORDER BY LENGTH(string)

Tutaj także możesz przerzucić funkcjÄ™ do zbioru wyników, by znalazÅ‚a siÄ™ tam i dÅ‚ugość tekstu.

MySQL może na tekÅ›cie przeprowadzać wiele innych ciekawych operacji za pom. funkcji. Ze wzglÄ™dów praktycznych ograniczÄ™ ich listÄ™ do minimum:

LEFT(tekst, d) - Zwraca d pierwszych liter z podanego tekstu.
RIGHT(tekst, d) - Zwraca d liter z podanego tekstu licząc od końca.
LOWER(tekst) - Zwraca tekst, w którym wszystkie duże litery zostały zamienione na małe.
UPPER(tekst) - Zwraca tekst, w którym wszystkie małe litery zostały zamienione na duże.
LTRIM(tekst) - Jeśli w tekście na początku znajdowały się jakieś spacje, zostaną one ucięte.
RTRIM(tekst) - Z funkcji tej możemy skorzystać, gdy chcemy usunąć z podanego tekstu spacje znajdujące się na końcu łańcucha (przydaje się szczególnie przy polach typu CHAR, gdyż tam jeśli tekst nie jest podanej długości, reszta uzupełniana jest spacjami).
MID(tekst, p, d) - Działa tak samo, jak funkcja substr() w PHP. Jeśli nie wiesz jednak, jak ona działa, wyjaśniam. Funkcja zwraca d znaków z podanego tekstu zaczynając od pozycji p.
MD5(tekst) - Generuje 32-znakową sumę kontrolną (hashuje) na podstawie otrzymanego tekstu algorytmem MD5. Jest on używany przez wiele skryptów do "szyfrowania" haseł, gdyż po takim ich potraktowaniu nie da się ich odtworzyć.

Funkcje można także stosować w zapytaniach innych, niż SELECT:

INSERT INTO tabela (pole1, pole2) VALUES(UPPER('tekst tekst'), LOWER('TEKST TEKST'));

Do góry

Aliasy, tworzenie relacji

MySQL jest relacyjnÄ… bazÄ… danych. Niestety wielu (zwÅ‚aszcza poczÄ…tkujÄ…cych) jej użytkowników zupeÅ‚nie nie rozumie tego terminu. SpróbujÄ™ wytÅ‚umaczyć go na przykÅ‚adzie. ZaÅ‚óżmy, że katalogujesz dane o rozmaitych albumach muzycznych. Utworzysz dla nich tabelkÄ™ albumy, w której chcesz zawrzeć tytuÅ‚ albumu, wykonawcÄ™, rok wydania oraz jego zawartość. Tu pojawia siÄ™ jednak poważny problem - stwierdzenie, iż wszystkie albumy zawierajÄ… tÄ™ samÄ… ilość utworów, jest absurdem. Jak zatem odwzorować ich listÄ™ w tabeli? Cóż... można utworzyć 10 pól, a gdyby utworów byÅ‚o wiÄ™cej - dla jednego albumu tworzyÅ‚oby siÄ™ dwa rekordy. Kolejny problem - gdy albumów bÄ™dzie nie 10, nie 20, a np. 17? Część miejsca zmarnuje siÄ™. Podobna sytuacja wystÄ…pi, gdyby album byÅ‚ tworzony przez kilku wykonawców.

Na szczęście MySQL umożliwia proste tworzenie relacji - nic nie stoi na przeszkodzie, byÅ› utworzyÅ‚ osobnÄ… tabelkÄ™ dla utworów, do której dodasz pole przechowujÄ…ce ID albumu, do którego tenże przynależy. Ba - możesz posunąć siÄ™ jeszcze dalej - utworzyć dwie tabelki: jednÄ… z nazwÄ… utworu, a drugÄ… łączÄ…cÄ… utwór z jakimÅ› albumem - wtedy bÄ™dziesz w stanie obsÅ‚użyć sytuacjÄ™, gdy jeden utwór znajdzie siÄ™ na kilku albumach. Samo tworzenie tabelek to nie wszystko (wszak takie coÅ› to da radÄ™ także na murze kredÄ… napisać :)). MySQL daje nam bowiem możliwość operowania na wielu tabelach w jednym zapytaniu! I to jest wÅ‚aÅ›nie relacja. Bez wzglÄ™du na to, w ilu tabelach przechowujesz dane - w prawie każdej sytuacji bÄ™dziesz w stanie je pobrać jednym zapytaniem.

ZostaÅ„my przy naszej bazie albumów. Na niej chciaÅ‚bym zademonstrować dziaÅ‚anie relacji w praktyce. Na poczÄ…tek przygotujmy tabelki i wrzućmy do nich jakieÅ› dane:

CREATE TABLE `albumy` (
   `id` int(8) NOT NULL AUTO_INCREMENT,
   `tytul` varchar(64) NOT NULL DEFAULT '',
   `wykonawca` varchar(32) NOT NULL DEFAULT '',
   `rok_wydania` int(6) NOT NULL DEFAULT '0',
   PRIMARY KEY (`id`)
);
 
INSERT INTO `albumy` VALUES (1, 'Nightflight to Venus', 'Boney M', 1978);
INSERT INTO `albumy` VALUES (2, 'Love for sale', 'Boney M', 1977);
INSERT INTO `albumy` VALUES (3, 'Spirits Having Flown', 'Bee Gees', 1979);
 
CREATE TABLE `utwory` (
   `id` int(8) NOT NULL AUTO_INCREMENT,
   `tytul` varchar(64) NOT NULL DEFAULT '',
   `wykonawca` varchar(32) NOT NULL DEFAULT '',
   `rok_wydania` int(6) NOT NULL DEFAULT '0',
   PRIMARY KEY (`id`)
);
 
INSERT INTO `utwory` VALUES (1, 'Rasputin', 'Boney M', 1978);
INSERT INTO `utwory` VALUES (2, 'Nightflight to Venus', 'Boney M', 1978);
INSERT INTO `utwory` VALUES (3, 'Daddy Cool', 'Boney M', 1976);
INSERT INTO `utwory` VALUES (4, 'Tragedy', 'Bee Gees', 1979);
INSERT INTO `utwory` VALUES (5, 'Too much heaven', 'Bee Gees', 1979);
 
CREATE TABLE `album_utwor` (
   `album_id` int(8) NOT NULL DEFAULT '0',
   `utwor_id` int(8) NOT NULL DEFAULT '0',
   KEY `album_id` (`album_id`,`utwor_id`)
) TYPE=MyISAM;
 
INSERT INTO `album_utwor` VALUES (1, 1);
INSERT INTO `album_utwor` VALUES (1, 2);
INSERT INTO `album_utwor` VALUES (2, 3);
INSERT INTO `album_utwor` VALUES (3, 4);
INSERT INTO `album_utwor` VALUES (3, 5);
# Male info dla fanow Bee Gees'ow - gdyby byl jakis blad
# w dacie wydania albumu/utworow, prosze mnie zawiadomic; 

A więc mamy strukturę tabelek będącą odpowiednikiem tego, co pisałem powyżej. Aby dodatkowo nie gmatwać wszystkiego, uprościłem gromadzenie danych o wykonawcy.

Spróbujmy teraz pobrać jakieÅ› dane. ZaÅ‚óżmy, że chcemy sprawdzić, w jakim albumie ukazaÅ‚ siÄ™ utwór Rasputin. Możemy zrealizować to takim zapytaniem:

SELECT a.tytul, a.wykonawca FROM albumy a, utwory u, album_utwor au WHERE a.id = au.album_id AND au.utwor_id = u.id AND u.tytul = 'Rasputin';

Wynik:

TYTUL                  WYKONAWCA
Nightflight to Venus   Boney M

W zapytaniu pojawiÅ‚o nam siÄ™ sporo nowych elementów. Najbardziej rzuca siÄ™ w oczy rozbudowanie klazuli FROM, wiÄ™c to od niej zaczniemy. WymieniÅ‚em tam listÄ™ tabel, którymi bÄ™dÄ™ siÄ™ posiÅ‚kować w moich zmaganiach. Ale - po nazwie każdej w tabel podaÅ‚em alias, czyli krótki identyfikator, którego użyjÄ™ dalej do okreÅ›lania, że to wÅ‚aÅ›nie o tÄ™, a nie o innÄ… tabelkÄ™ mi chodzi. W spisie danych do pobrania zaznaczyÅ‚em przy polu, że dane majÄ… być pobrane z tabeli albumy, nie z utwory (tam bowiem wystÄ™pujÄ… takie same pola). Najpierw podaÅ‚em nazwÄ™ tabelki, z tym że pod postaciÄ… aliasu, po czym oddzieliÅ‚em jÄ… kropkÄ… od nazwy pola.

OK, baza wie, co ma pobrać i skÄ…d. Jednak aby wiedziaÅ‚a, JAKIE sÄ… zależnoÅ›ci miÄ™dzy poszczególnymi tabelami, potrzebna jest nam klazula WHERE. Ta także ulegÅ‚a rozbudowaniu. Zobacz, jak połączone sÄ… tabele. Przy pomocy zwykÅ‚ego operatora porównania = okreÅ›liÅ‚em, iż w zbiorze wyników znajdÄ… siÄ™ rekordy albumu skojarzonego poprzez tabelÄ™ album_utwor (a.id = au.album_id AND au.utwor_id = u.id) z piosenkÄ… o tytule Rasputin.

Teraz coÅ› trudniejszego - pragniemy pobrać tytuÅ‚y wszystkich piosenek wraz z nazwami albumów, w jakich siÄ™ ukazaÅ‚y, posortowane wedÅ‚ug daty ich premiery. Tabele albumy oraz utwory zostanÄ… skojarzone w taki sam sposób, jak w poprzednim przykÅ‚adzie.

SELECT u.tytul, u.wykonawca, a.tytul AS `album_tytul` FROM albumy a, utwory u, album_utwor au WHERE a.id = au.album_id AND au.utwor_id = u.id ORDER BY u.rok_wydania

Wykonaj to zapytanie i zobacz, że to dziaÅ‚a! PobraliÅ›my naraz dane z dwóch tabel. Zwróć uwagÄ™ na interesujÄ…ce AS wystÄ™pujÄ…ce w spisie danych do pobrania - dziÄ™ki niemu okreÅ›liÅ‚em, że w zbiorze wyników dane o albumie, z którego pochodzi utwór, bÄ™dÄ… widoczne pod tytuÅ‚em album_tytul.

Mam nadziejÄ™, iż przekonaÅ‚em CiÄ™ o potÄ™dze relacji oraz o możliwoÅ›ciach, jakie one stwarzajÄ…. Przy ich pomocy znaczÄ…co zmniejszysz liczbÄ™ zapytaÅ„ w twoich skryptach. Jako ciekawostkÄ™ podam, iż relacje można stosować NIE TYLKO w klauzulach SELECT! Oto przykÅ‚ady wprost z moich wÅ‚asnych zasobów :)

UPDATE users, sessions SET user_lastvisit = sessions.session_time WHERE sessions.session_time < (UNIX_TIMESTAMP() - 3600);
DELETE sessions FROM users, sessions WHERE users.user_id = sessions.session_user_id;

Do góry

LEFT JOIN

Relacje sÄ… potężnym narzÄ™dziem. StanÄ… siÄ™ jednak jeszcze potężniejsze, gdy nauczysz siÄ™ dynamicznie ustalać, czy jakieÅ› dane znajdÄ… siÄ™ w koÅ„cowych wynikach, czy nie. Co mam na myÅ›li pod sÅ‚owem "dynamiczne"? Otóż... w poprzednim rozdziale pokazaÅ‚em zapytania z mojego wÅ‚asnego mechanizmu sesji sÅ‚użącego do autoryzacji. Każda sesja przechowuje ID zalogowanego aktualnie użytkownika, jednak ma także możliwość obsÅ‚ugi zwyczajnych goÅ›ci (wtedy pole z ww. ID jest ustawione na 0). Gdyby zastosować do czegoÅ› takiego zapytanie w tym stylu:

SELECT s.*, u.* FROM users u, sessions s WHERE s.session_user_id = u.user_id

to mielibyÅ›my maÅ‚e kÅ‚opoty, bowiem nie zostaÅ‚yby zaÅ‚adowane żadne sesje należące do goÅ›ci (zapytanie ma konkretny warunek: dodaj dane tylko, gdy pole session_user_id wskazuje na istniejÄ…cego użytkownika i vice versa). Tu wkracza ta nasza dynamiczność - zapytanie powinno tworzyć rekord z obu tabel tylko wtedy, gdy istnieje powiÄ…zanie z konkretnym użytkownikiem. W przeciwnym wypadku dane z users nie powinny być dołączone do wyniku, w którym zostanÄ… tylko informacje o sesji. Tylko jak to zrobić? MySQL ma receptÄ™ i na to...

Klauzule JOIN pozwalajÄ… na definiowanie warunków, kiedy informacje z takiej a takiej tabelki majÄ… być dodawane do zbioru wyników, a kiedy nie, bez wpÅ‚ywania na to, czy dany rekord trafi tam. Istnieje dość dużo typów JOIN'ów, my zajmiemy siÄ™ tu tylko jednym - LEFT JOIN. Umieszczamy go zawsze w klauzuli FROM. Jego skÅ‚adnia jest nastÄ™pujÄ…ca:

tabelka1 LEFT JOIN tabelka2 ON warunek

tabelka1 będzie obecna w każdym rekordzie, natomiast tabelka2 tylko wtedy, gdy zostanie spełniony jakiś warunek.

W naszych sesjach wyglądałoby to tak:

SELECT s.*, u.* FROM users u LEFT JOIN sessions s ON s.session_user_id = u.user_id

Oczywiście warunek kojarzący obie tabele został przeniesiony do LEFT JOIN'a, ponieważ inaczej raczej nic ciekawego nie dałoby nam to zapytanie :).

UWAGA: zapytania używające LEFT JOIN nie powinny być stosowane dla dużej ilości danych naraz, gdyż spowodują wtedy zbyt duże obciążenie bazy, co odbije się niekorzystnie na wydajności aplikacji!

Do góry

Właściwy typ danych

To zagadnienie można potraktować jako dodatek, aczkolwiek jest ono również niezwykle ważne. Dotyczy bowiem wyboru wÅ‚aÅ›ciwego typu dla przechowywanych informacji, co wiąże siÄ™ bezpoÅ›rednio z przyspieszeniem wykonywania rozmaitych operacji na tabeli oraz samÄ… wygodÄ… użytkowania i analizowania danych. W kursach niestety jest traktowane (tradycyjnie) po Å‚ebkach, a tymczasem te sÄ… czytane niejednokrotnie przez osoby, które jeszcze nie znajÄ… siÄ™ na programowaniu, a co za tym idzie, nie wiedzÄ…, jak efektywnie swe dane przechowywać!

SpotkaÅ‚em siÄ™ raz z sytuacjÄ…, gdy pewien poczÄ…tkujÄ…cy programista PHP-MySQL zapisywaÅ‚ dosÅ‚ownie wszystkie liczby do bazy jako zwyczajne teksty (varchar) i po prostu nie rozumiaÅ‚, po co w takim razie udostÄ™pniono tyle innych typów. Tymczasem sprawa tutaj ma znaczenie zasadnicze, ponieważ liczba zapisana jako liczba zajmuje o wiele mniej pamiÄ™ci - komputer koduje jÄ… sobie po prostu w postaci zbioru jedynek i zer, a dziesiÄ™tna reprezentacja go (krótko mówiÄ…c) nie interesuje. Porównajmy - w jednym bajcie możemy zapisać jednÄ… z 256 wartoÅ›ci. Nadaje siÄ™ to idealnie do reprezentowania liczb z zakresu 0-255 i, jak widać, bez wzglÄ™du na ilość cyfr, ciÄ…gle kodujemy to na tej samej powierzchni. Gdyby każdÄ… cyfrÄ™ zapisywać jako znak tekstowy, dla liczb powyżej setki bÄ™dÄ… to już trzy bajty, plus dodatkowy bajt przechowujÄ…cy dÅ‚ugość tekstu. Dla np. 1000 rekordów daje to istotnÄ… stratÄ™ ponad 3 kb! Weźmy teraz wiÄ™kszÄ… liczbÄ™. 4 bajty to 32 bity. 2 do potÄ™gi 32 daje zakres 0 - 4294967296, czyli ponad 4 miliardy kombinacji! W miliardzie mamy 10 cyfr, wiÄ™c w przypadku zapisu tekstowego zajmiemy 11 bajtów (10 na liczbÄ™ + 1 na dÅ‚ugość tekstu). 11 - 4 = 7 bajtów różnicy na korzyść zapisu binarnego! Przemnóżmy to przez 1000 rekordów, a otrzymamy 4 kb i 11 kb! Różnica jest chyba widoczna goÅ‚ym okiem?

Warto wspomnieć również, iż komputerowi o wiele Å‚atwiej jest porównywać cyfrowo zapisane numery - w koÅ„cu całą sprawa polega tu na odpowiednim porównaniu bitów na kilku bajtach. Tymczasem w przypadku tekstu po prostu nie ma takiej możliwoÅ›ci - dla komputera jest to tylko zbiór liczb oznaczajÄ…cych konkretne znaki graficzne, czyli pojedyncze cyfry. Nie potrafi on z tym zrobić nic, a gdyby spróbować to porównać, zrobi to tak, jak porównuje siÄ™ ciÄ…gi, tj. bajt po bajcie. Co z tego wynika? Ograniczymy sobie w ten sposób tylko możliwoÅ›ci dziaÅ‚ania (wyszukiwanie wzglÄ™dem tego, czy rekordy majÄ… np. mniejszÄ… wartość - odpada), a to, co zostaje, zajmuje nieporównywalnie wiÄ™cej czasu.

Daty także warto zapisywać w postaci liczb. Komputer nawet uÅ‚atwia nam tu robotÄ™, ponieważ bez problemu potrafi on okreÅ›lić liczbÄ™ sekund, które upÅ‚ynęły od 1.1.1970, wÅ‚aÅ›nie w postaci liczby jako liczby :). Porównywanie tak zaprezentowanego czasu to czysta przyjemność; nawet wbudowany w MySQL'a typ date nie ma przy tym szans.

Do góry

Zakończenie

ArtykuÅ‚ powoli dobiega koÅ„ca. Mam nadziejÄ™, iż w znaczÄ…cy sposób poszerzyÅ‚em TwÄ… wiedzÄ™ o jÄ™zyku SQL dla bazy MySQL, a to zaowocuje znacznie lepszymi zapytaniami w Twoich projektach. W koÅ„cu wydajność jest najważniejsza...

Autor: Tomasz "Zyx" Jędrzejewski, www.zyxist.com.

Do góry

Waszym zdaniem:

Uwaga: To jest jedynie część artykułu. Komentarze dotyczą całości tekstu.

Nikt jeszcze nie dodał swojego komentarza. Możesz być pierwszy!


Twoim zdaniem:

Pola wymagane: podpis, adres e-mail, treść. Znaczniki HTML nie są dozwolone.

Reklama

banner

Partnerzy

CityDesign.pl