Autor: Tomasz Jędrzejewski
Data publikacji: 12.11.2003, 09:16 | Ostatnia modyfikacja: 29.10.2006, 16:21
Z bazÄ… danych SQLite, oraz jej obsÅ‚ugÄ… w PHP warto zapoznać siÄ™ z dwóch powodów. Pierwszy - nie wymaga instalacji Å»ADNYCH dodatkowych programów; Drugi - od PHP 5 zastÄ…pi ona MySQL'a jako wbudowany na staÅ‚e moduÅ‚ obsÅ‚ugi baz danych.
Z pewnoÅ›ciÄ… wielu chciaÅ‚oby przetestować możliwoÅ›ci innych systemów baz danych. Na ich drodze piÄ™trzy siÄ™ jednak sporo trudnoÅ›ci: brak dostÄ™pu do odpowiednich materiaÅ‚ów, maÅ‚a popularność danej bazy, a wreszcie trudnoÅ›ci ze zwykle skomplikowanÄ… instalacjÄ… odpowiednich programów zewnÄ™trznych. Jednak od niedawna PHP dysponuje znakomitym narzÄ™dziem, które nadaje siÄ™ do wiÄ™kszoÅ›ci zwykÅ‚ych zastosowaÅ„, a przy tym praktycznie nie wymaga instalacji. Chodzi o SQLite i tym pakietem zajmiemy siÄ™ w czytanym przez Ciebie tekÅ›cie.
SQLite bardzo dobrze wspiera zapewne znany ci już jÄ™zyk komunikacji z bazami danych zwany SQL (Structured Query Language). Co wiÄ™cej - obsÅ‚uguje kilka rozwiÄ…zaÅ„, których np. MySQL'owi po prostu brakuje:
Jednak najważniejszym faktem dla Ciebie, programisty PHP jest to, że nie będziesz musiał instalować praktycznie żadnego dodatkowego serwera tejże bazy! Dlaczego? Bo sam moduł php_sqlite jest tym serwerem, a to oznacza, że serwer SQLite jest po prostu wbudowany w PHP :). Ponadto od PHP 5 będzie on już na stałe w niego wbudowany i nie będzie możliwości wyłączyć jego obsługi (do niedawna miejsce to dzierżył moduł dla baz MySQL).
SQLite przechowuje swoje dane w jednym pliku, który zwykle znajduje siÄ™ zaraz obok twoich skryptów PHP. Nie jest on jednak szyfrowany i dlatego SQLite niezbyt nadaje siÄ™, jeÅ›li zależy Ci na bezpieczeÅ„stwie swych danych. Z drugiej strony dziÄ™ki takiemu krokowi, wedÅ‚ug zapewnieÅ„ autorów, SQLite dziaÅ‚a prawie dwa razy szybciej niż MySQL i PostgreSQL. Tak wiÄ™c jeżeli bezpieczeÅ„stwo stawiasz na miejscu pierwszym - wybierz innÄ… bazÄ™ danych. W przeciwnym wypadku - warto spróbować. Być może kiedyÅ› bÄ™dzie to dla Ciebie jedyna alternatywa, gdy dostaniesz serwer bez obsÅ‚ugi żadnych innych baz danych?
MuszÄ™ także wspomnieć o pewnej rzeczy zwiÄ…zanej z obsÅ‚ugÄ… jÄ™zyka SQL w omawianej bazie - zapytania ALTER musisz odÅ‚ożyć na póÅ‚kÄ™, bowiem SQLite ich po prostu nie obsÅ‚uguje.
Instrukcje odnoÅ›nie instalacji w systemach Unix znajdujÄ… siÄ™ w pliku INSTALL dostÄ™pnym razem z pakietem kodu źródÅ‚owego PHP. Posiadacze PHP 5 mogÄ… spokojnie ten rozdziaÅ‚ pominąć, natomiast ci, którzy używajÄ… wciąż PHP 4 (UWAGA: Koniecznie wersja 4.3.x; jeÅ›li masz starszÄ…, to zrób update i nie popeÅ‚niaj wiÄ™cej grzechu zaniedbania tejże kwestii :)), muszÄ… pobrać plik php_sqlite.dll spod adresu: http://snaps.php.net/win32/PECL_STABLE/php_sqlite.dll, skopiować go do folderu "extensions" w katalogu ze swoim PHP, po czym otworzyć plik php.ini. Tam należy zlokalizować zbiór linijek z napisami "extension=php_xxx.dll" i pod nim dopisać "extension=php_sqlite.dll".
Na poczÄ…tku zapoznamy siÄ™ z samym "łączeniem siÄ™" do bazy SQLite, oraz zakÅ‚adaniem w niej tabel. Połączenie realizujemy przy pomocy funkcji sqlite_open(nazwa_pliku). Jako parametr podajemy nazwÄ™ pliku, w której znajduje siÄ™ baza danych, natomiast po jej wykonaniu otrzymujemy uchwyt połączenia, który bÄ™dzie potrzebny praktycznie we wszystkich innych funkcjach. Połączenie zamykamy poleceniem sqlite_close(), gdzie za parametr podajemy uchwyt. Zapytanie możemy wysÅ‚ać komendÄ… sqlite_query(uchwyt, zapytanie). A oto przykÅ‚ad numer 1, dziÄ™ki któremu stworzymy bazÄ™ potrzebnÄ… nam w późniejszych przykÅ‚adach.
<?php $db = sqlite_open('./sklep.sqlite'); sqlite_query($db,' CREATE TABLE products ( product_id INTEGER PRIMARY KEY, product_name VARCHAR(32), product_desc BLOB, product_price FLOAT ); CREATE TABLE clients ( client_id INTEGER PRIMARY KEY, client_name, client_address ); INSERT INTO products VALUES (NULL, \'Spadochron do nurkowania\', \'Rewelacyjny spadochron dla nurków! Znakomicie zapobiega zderzeniu się z dnem!\', 250.99); INSERT INTO products VALUES (NULL, \'Wanna z drzwiami jednoskrzyd3owymi\', \'Dzięki wbudowanym drzwiom już wiecej nie pośliźniesz się wychodząc z wanny\', 599.8); INSERT INTO products VALUES (NULL, \'Używany papier toaletowy\', \'Tani, wygodny, ekologiczny (bo z odzysku) - czyli to, co ekolodzy lubią najbardziej\', 1.99); INSERT INTO clients VALUES (NULL, \'Mietek Żulewicz\', \'Prosektorium przy ul. Ofiar Hiroszimy; 00-000 Pcim Dolny\'); INSERT INTO clients VALUES (NULL, \'Jan Kowalski\', \'ul. Stołeczna 13, 99-999 Bździągowo\'); '); echo 'Baza danych SQLite założona pomyślnie!'; sqlite_close($db); ?>
Przyjrzyj siÄ™ wywoÅ‚aniu funkcji sqlite_query(), gdyż przedstawia ono parÄ™ ciekawych cech SQLite'a. Pierwsza z nich - możemy wysÅ‚ać dowolnie dużo zapytaÅ„ wywoÅ‚ujÄ…c funkcjÄ™ tylko raz - w tym wypadku kolejne zapytania oddzielone sÄ… Å›rednikami. Druga sprawa dotyczy samego tworzenia tabel (CREATE TABLE). Zauważ - w tabeli "clients" nie zdefiniowaliÅ›my typów dla pól. To nie jest błąd, bowiem SQLite dopuszcza takie rozwiÄ…zanie, a dziaÅ‚a ono podobnie do zmiennych w PHP - typ dobierany jest automatycznie. Warto zwrócić także uwagÄ™ na "INTEGER PRIMARY KEY". Kod ten informuje SQLite, że dane pole ma zawierać sekwencjÄ™. W PostgreSQL'u odpowiada to typowi SERIAL, natomiast w MySQL'u parametrowi AUTO_INCREMENT. Jeżeli chcesz zapoznać siÄ™ ze wszystkimi dostÄ™pnymi typami, wejdź pod adres http://sqlite.org/datatypes.html.
W tej sferze SQLite nie oferuje zbyt wielu niespodzianek - praktycznie wszystko przebiega podobnie, jak w innych bazach. Oto przykład:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Sklep</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2"> </head> <body> <center>Produkty</center> <?php $db = sqlite_open('./sklep.sqlite'); $r = sqlite_query($db, 'SELECT * FROM products'); echo 'Znaleziono '.sqlite_num_rows($r).' produktów<br>'; while($row = sqlite_fetch_array($r, SQLITE_ASSOC)){ echo '<b>'.$row['product_id'].'. '.$row['product_name'].'</b><br> Opis: '.$row['product_desc'].'<br>Cena: '.$row['product_price'].'<br><br>'; } ?> <hr> <center>Klienci</center> <?php $r = sqlite_query($db, 'SELECT * FROM clients'); echo 'Znaleziono '.sqlite_num_rows($r).' klientów<br>'; while($row = sqlite_fetch_array($r, SQLITE_ASSOC)){ echo '<b>'.$row['client_id'].'. '.$row['client_name'].'</b><br> Adres: '.$row['client_address'].'<br><br>'; } sqlite_close($db); ?> </body> </html>
W zasadzie można zainteresować siÄ™ tu tylko drugim parametrem funkcji sql_fetch_array(). OkreÅ›la on, jak zostanÄ… okreÅ›lone nazwy pól w tabeli wynikowej. SQLITE_ASSOC oznacza, że bÄ™dziemy mogli odwoÅ‚ywać siÄ™ poprzez nazwy pól: $row['nazwa_pola']; SQLITE_NUM oznacza numeryczne oznaczenia: $row[1]; natomiast SQLITE_BOTH spowoduje, że tabela wynikowa bÄ™dzie zawieraÅ‚a i jedne, i drugie odwoÅ‚ania.
Widok to po prostu pojedyncze zapytanie SELECT, którego możemy używac tak jak zwykłą tabelÄ™ (z tego powodu jest zwany czasem wirtualnÄ… tabelÄ…). Stworzyć go możemy poleceniem CREATE VIEW nazwa AS zapytanie_select tak, jak w poniższym przykÅ‚adzie:
<?php $db = sqlite_open('./sklep.sqlite'); sqlite_query($db, ' CREATE TABLE products_to_clients (product_id, client_id); INSERT INTO products_to_clients VALUES(1,2); INSERT INTO products_to_clients VALUES(3,2); INSERT INTO products_to_clients VALUES(2,1); CREATE VIEW client_products AS SELECT pc.client_id AS client_id, p.product_name AS product_name FROM products_to_clients pc, products p WHERE pc.product_id = p.product_id;'); $r = sqlite_query($db, 'SELECT product_name FROM client_products WHERE client_id=2'); echo 'Produkty zamówione przez klienta 2:<br>'; while($row = sqlite_fetch_array($r, SQLITE_ASSOC)){ echo $row['product_name'].'<br>'; } $r = sqlite_query($db, 'SELECT product_name FROM client_products'); echo '<br><br>Wszystkie zamówione przynajmniej raz produkty<br>'; while($row = sqlite_fetch_array($r, SQLITE_ASSOC)){ echo $row['product_name'].'<br>'; } sqlite_close($db); ?>
StworzyliÅ›my tutaj widok "client_products". Mówi on nam, że posiada dwa pola: client_id, oraz product_name (fragment SELECT pc.client_id AS client_id, p.product_name AS product_name). Wartość client_id pobierana jest z tabeli products_to_orders (informacje o tym, jakie produkty zamówiÅ‚ jaki klient), natomiast product_name z tabeli products, ale tylko wtedy, gdy speÅ‚nia warunek WHERE. Użytkownik nie widzi caÅ‚ego tego kombinowania z tym, skÄ…d pobrać wartoÅ›ci jakich pól - dla niego client_products to najzwyklejsza w Å›wiecie tabelka z dwoma polami. Jedynym "dziwnym" jej zachowaniem jest to, że jeżeli zmienimy nazwÄ™ jakiegoÅ› produktu w tabeli "products", to "zmieni" siÄ™ ona także w widoku.
UWAGA: W SQLite dozwolone jest tylko i wyłącznie czytanie z widoków - tak wiÄ™c naszego "client_products" nie bÄ™dziemy mogli użyć w zapytaniach typu INSERT, UPDATE, czy DELETE.
Widoki usuwamy poleceniem DROP VIEW nazwa_widoku.
Co to jest indeks, wie każdy, kto zabawiaÅ‚ siÄ™ PostgreSQL'em, czy MySQL'em - po prostu jest to kopia danego pola, gdzie wszystkie wartoÅ›ci sÄ… już posortowane, tak wiÄ™c przyspiesza siÄ™ wyszukiwanie, czy sortowanie, natomiast spowalnia dodawanie rekordów (z racji tego, że trzeba jeszcze zmodyfikować indeks), a także zwiÄ™ksza siÄ™ objÄ™tość bazy. Indeksów nie można zakÅ‚adać, jak popadnie - nie da siÄ™ też tego dobrze opisać. Dlatego proponujÄ™ pooglÄ…dać, gdzie sÄ… one zaÅ‚ożone w bazacch danych różnych znanych skryptów, np. phpBB, czy InvisionBoard. Indeks tworzymy w sposób przedstawiony na przykÅ‚adzie:
<?php $db = sqlite_open('./sklep.sqlite'); sqlite_query($db, 'CREATE INDEX i ON products_to_clients (client_id, product_id);'); if(!sqlite_last_error($db)){ echo 'Indeks dodany!'; }else{ echo 'Błąd SQLite: '.sqlite_error_string(sqlite_last_error()); } sqlite_close($db); ?>
Jedyne, co musimy określić w zapytaniu CREATE INDEX to nazwa indeksu, tabela, na jaką chcemy go założyć, oraz pola, jakich ma on dotyczyć.
PrzykÅ‚ad ten ilustruje dodatkowo, jak należy sprawdzać błędy w naszych zapytaniach. Jest to bardzo ważne, ponieważ czÄ™sto nieutworzenie uchwytu do wyników przez funkcjÄ™ sqlite_query() spowodowane jest wÅ‚aÅ›nie błędem w naszym zapytaniu!
Trigger jest to zadanie (jedno, lub wiÄ™cej zapytaÅ„ SQL) wykonujÄ…ce siÄ™ w tle (czyli programista nawet nie wie, że ono siÄ™ wywoÅ‚aÅ‚o), gdy zajdzie okreÅ›lona okoliczność (np. dodanie rekordu do bazy). Daje nam on potężne możliwoÅ›ci. ZaprezentujÄ™ je na podstawie dwóch poniższych przykÅ‚adów. Pierwszy z nich utworzy trigger, natomiast drugi pokaże efekt jego dziaÅ‚ania :). Pod ogieÅ„ weźmiemy tabelÄ™ products_to_clients oraz clients. Co tu może dziać siÄ™ ciekawego? Ano, można usunąć klienta z bazy. Wtedy jednak zostanÄ… Å›mieci w pierwszej tabelce. Mamy dwa wyjÅ›cia - albo rÄ™cznie je usunąć, albo utworzyć trigger, który zrobi to za nas bez nas :).
<?php $db = sqlite_open('./sklep.sqlite'); sqlite_query($db, ' CREATE TRIGGER delete_orders BEFORE DELETE ON clients FOR EACH ROW BEGIN DELETE FROM products_to_clients WHERE client_id = OLD.client_id; END; '); if(!sqlite_last_error($db)){ echo 'Trigger dodany!'; }else{ echo 'Błąd SQLite: '.sqlite_error_string(sqlite_last_error()); } sqlite_close($db); ?>
Co my tu mamy? Ano, zdefiniowany trigger "delete_orders", który wykona siÄ™ przed skasowaniem rekordów z tabeli clients (BEFORE DELETE ON clients). Dla każdego kasowanego rekordu (FOR EACH ROW) wykona on zapytanie DELETE FROM products_to_clients WHERE client_id = OLD.client_id;, które usunie z tabeli products_to_orders odnoÅ›niki do produktów zamówionych przez klienta "OLD.client_id". OLD jest aliasem. SQLite daje nam jeszcze jeden taki alias: NEW, a oto ich użycie:
Polecam zapoznać siÄ™ ze skÅ‚adniÄ… polecenia CREATE TRIGGER na stronie: http://sqlite.org/lang.html#createtrigger, oraz w ogóle samodzielnie poeksperymentować z tym. ObsÅ‚uga triggerów jest tematem na caÅ‚y obszerny artykuÅ‚, a ja nie zamierzam siÄ™ aż tak tu rozpisywać :).
Ale wróćmy do rzeczywistoÅ›ci. Warto pokazać, co nasz trigger takiego potrafi:
<?php $db = sqlite_open('./proba_sklep.sqlite'); sqlite_query($db, 'DELETE FROM clients WHERE client_id = 2'); if(!sqlite_last_error($db)){ echo 'Klient 2 usunięty!'; } $r = sqlite_query($db, 'SELECT * FROM products_to_clients'); echo 'Kontrola zamówień:<br>'; while($row = sqlite_fetch_array($r, SQLITE_ASSOC)){ echo 'Klient '.$row['client_id'].' zamówił produkt o ID '.$row['product_id'].'<br>'; } sqlite_close($db); ?>
Uruchom przykÅ‚ad - co siÄ™ staÅ‚o? UsunÄ™liÅ›my tylko klienta z tabeli "clients", natomiast w tabeli "products_to_orders" nie ma także już informacji o zamówionych przez niego produktach. Po prostu w tle zadziaÅ‚aÅ‚ trigger, który posprzÄ…taÅ‚ za nas automatycznie.
Aby stworzyć własną funkcję w PostgreSQL'u, należało być w nim samym nieźle otrzaskanym, gdyż zadanie to jest tam trudniejsze, niż przybicie galaretki do ściany :). Jednak SQLite jest wbudowany w PHP, nic nie stoi zatem na przeszkodzie, by korzystać z... udostępnianych przez niego funkcji. Oto przykład:
<?php function tralala(){ return 'Jestem sobie funkcjÄ… PHP-SQLite!'; } $db = sqlite_open('./sklep.sqlite'); sqlite_create_function($db, 'nowa_funkcja', 'tralala'); $r = sqlite_query($db, 'SELECT nowa_funkcja() AS wynik;'); $row = sqlite_fetch_array($r, SQLITE_ASSOC); echo $row['wynik']; sqlite_close($db); ?>
UtworzyliÅ›my w PHP funkcjÄ™ "tralala", a nastÄ™pnie poleceniem sqlite_create_function poinformowaliÅ›my SQLite, że jeÅ›li natrafi w zapytaniu na funkcjÄ™ "nowa_funkcja", to ma wykonać stworzonÄ… przez nas "tralala". Niżej z resztÄ… widać, iż sposób ten po prostu dziaÅ‚a.
To już koniec artykułu o bazie SQLite. Mam nadzieję, że zainteresowałem Cię tym rozwiązaniem, zwłaszcza, że jest ono tego warte. Nie poprzestawaj na tym. Oto dwa bardzo przydatne adresy, pod którymi znajdziesz więcej informacji:
Jednocześnie zapewniam, że SQLite będzie często powracać w moich artykułach.
Autor: Tomasz "Zyx" Jędrzejewski, www.zyxist.com
Waszym zdaniem:
Nikt jeszcze nie dodał swojego komentarza. Możesz być pierwszy!