Mam do wykonania parę zadań. Jedno z nich to napisanie funkcji/procedury w MySQL. Jak dla mnie to jest (a właściwie była) czarna magia. Troszkę czasu na to przeznaczyłem i okazało się, że takie coś wcale nie jest straszne ;-]
Podobnie jak PHP, C++, Java i masa innych jÄ™zyków programowania ma procedury i/lub funkcje. Jak wiadomo mogÄ… one posÅ‚użyć do wielu różnych rzeczy. W MySQL można je wykorzystać aby zwiÄ™kszyć bezpieczeÅ„stwo. Zapisana funkcja/procedura może być bez problemu wywoÅ‚ana z poziomu PHP, ponieważ wystarczy wysÅ‚ać zapytanie do bazy bÄ™dÄ…ce wywoÅ‚aniem funkcji/procedury. W dalszej części postaram siÄ™ nieco opisać te dwie rzeczy.
Getting Started
Podczas korzystania z funkcji mogą wystąpić pewne problemy.
Jednym z ważniejszych jest znak Å›rednika ';', który jest wpisywany podczas tworzenia funkcji/procedury. Jest prosty sposób na obejÅ›cie tego. Wystarczy, że w konsoli mysql wpiszemy np "DELIMITER //", co spokojnie pozwoli nam na zapisanie kodu. Natomiast znak Å›rednika zastÄ…pi '//'. Tak wiÄ™c warto nie zapominać o tym, a na koÅ„cu warto wpisać "DELIMITER ;" tak aby Å›rednik miaÅ‚ swoje dawne "znaczenie".
Poza tym warto ustawić zmienna globalnÄ… log_bin_trust_function_creators na 1. Pozowli to nam na zapisywanie funkcji. Możemy siÄ™ do tego celu posÅ‚użyć poleceniem SET GLOBAL log_bin_trust_function_creators = 1;, które wpisujemy w konsoli mysql.
Na koniec dodam, że wygodnie jest pisać wszelkie funkcje w osobnym pliku *.sql, a później korzystać z polecenia "source nazwa_pliku" w konsoli mysql. W moim wypadku taki plik wyglÄ…da, mniej wiÄ™cej, tak:
delimiter //
CREATE FUNCTION nazwa () RETURNS VARCHAR(20)
BEGIN
RETURN 'jakis napis';
END
delimiter ;
Tabela
Szczerze mówiÄ…c nie jestem tego pewien, czy funkcja/procedura musi być przypisana do okreÅ›lonej bazy. Na cele tego kursu zrobimy nowÄ… bazÄ™ danych, z jednÄ… tabelÄ…. Później nam siÄ™ przyda ;]
Aby tego dokonać wystarczy, że do konsolki mysql wpiszemy:
CREATE DATABASE tmp;
create table skroty (skrot_id varchar(5),opis varchar(50));
insert into skroty values ('lol', 'lots of laught'), ('imho', 'in my humble opinion'), ('btw', 'by the way');
Funkcje
Ogólna budowa:
Ogólna budowa funkcji jest bardzo prosta. WyglÄ…da mniej wiÄ™cej tak:
CREATE FUNCTION ( ) RETURNS
BEGIN
{kod funkcji}
END
Prosty "Hello World"
To zacznijmy od oklepany przykÅ‚adu. Funkcja bÄ™dzie (póki co) wypisywaÅ‚a napis "Hello World". Najpierw przedstawiÄ™ kod, oraz sposób wywoÅ‚ania funkcji, a potem postaram siÄ™ opisać co i jak.
delimiter //
CREATE FUNCTION HelloWorld () RETURNS VARCHAR(20)
BEGIN
RETURN 'Hello World';
END
delimiter ;
Można ten kod zapisać do pliku, a potem wczytać poleceniem source, ale o tym pisałem już wcześniej ;-].
Wywołanie naszej funkcji polega na wpisaniu do konsoli mysql SELECT HelloWorld();. Efekt tego będzie taki:
mysql> select helloworld();
+--------------+
| helloworld() |
+--------------+
| Hello World |
+--------------+
1 row in set (0.00 sec)
Funkcja zwraca już gotowÄ… wartość. Polecenie SELECT sÅ‚uży do wypisania wyników dalszych poleceÅ„ (gdy wpiszemy np. SELECT NOW() pokaże nam siÄ™ aktualna data i godzina), także w tym wypadku wyÅ›wietli napis, który jest zwracany przez funkcjÄ™.
Mała dygresja
Jeżeli kod funkcji zapisujesz do pliku, a potem go Å‚adujesz go, to dopisz na poczÄ…tku coÅ› takiego: "DROP FUNCTION IF EXISTS HelloWorld;". SÅ‚uży to do kasowania funkcji HelloWorld() jeżeli istnieje. Ponieważ później w pliku jest ponowna deklaracja funkcji, to stara zostanie skasowana i od razy zastÄ…piona nowÄ….
W innym wypadku, trzeba będzie ręcznie kasować funkcję zanim zadeklarujemy ją na nowo (to samo polecenie co wyżej było wymienione). Jeśli tego nie zrobimy otrzymamy komunikat: "ERROR 1304 (42000): FUNCTION helloworld already exists"
Zmienne
W naszej funkcji możemy deklarować zmienne. Ich deklaracja wygląda tak: "DECLARE nazwa_zmiennej typ_zmiennej";
W naszym przykÅ‚adzie możemy siÄ™ posÅ‚użyć zmiennÄ… (np. jakis_napis), która bÄ™dzie przechowywaÅ‚a wartość, która później bÄ™dzie zwracana. Kod po zmianach wyglÄ…da tak:
DROP FUNCTION IF EXISTS HelloWorld;
delimiter //
CREATE FUNCTION HelloWorld () RETURNS VARCHAR(20)
BEGIN
DECLARE jakis_napis VARCHAR(20);
SET jakis_napis = 'Hello World';
RETURN jakis_napis;
END
delimiter ;
OczywiÅ›cie zmiennych może być wiÄ™cej. Możemy również takiej zmiennej przypisać wartość domyÅ›lnÄ…. Aby to uczynić podczas deklaracji należy dopisać "DEFAULT 'jakas wartosc' ". Także można powyższy kod nieco zmodyfikować, tak aby zmiennej od razu byÅ‚a przypisana wartość, która bÄ™dzie zwracana. W obu przypadkach wynik dziaÅ‚ania funkcji możemy sprawdzić w ten sam sposób, który zostaÅ‚ opisany wczeÅ›niej.
DROP FUNCTION IF EXISTS HelloWorld;
delimiter //
CREATE FUNCTION HelloWorld () RETURNS VARCHAR(20)
BEGIN
DECLARE jakis_napis VARCHAR(20) DEFAULT = 'Hello World';
RETURN jakis_napis;
END
delimiter ;
Parametry
Do funkcji również można wprowadzić parametr. Deklaracja tego parametru jest podczas tworenia funkcji (podobnie robi siÄ™ podczas tworzenia funkcji w innych jÄ™zykach programowania).
Deklaracja parametru następuje zaraz po podaniu nazwy funkcji (w nawiasach), czyli np: CREATE FUNCTION ( ) RETURNS
Poniższy przykÅ‚ad pokaże stworzenie funkcji, która odczyta zmienna paramter podawanÄ… przy woÅ‚ywaniu funkcji, oraz zwróci jej wartość.
DROP FUNCTION IF EXISTS HelloWorld;
delimiter //
CREATE FUNCTION HelloWorld (parametr VARCHAR(20)) RETURNS VARCHAR(20)
BEGIN
RETURN parametr;
END
delimiter ;
Przykład działania:
mysql> select HelloWorld('zlo');
+-------------------+
| HelloWorld('zlo') |
+-------------------+
| zlo |
+-------------------+
1 row in set (0.00 sec)
Procedury
Zastosowanie procedur jest nieco wiÄ™ksze. Aktualnie funkcje nie majÄ… prawa wykonywać poleceÅ„ SQL. Autorzy serwera przyznajÄ…, że takie ograniczenia powinny wkrótce zniknąć, ale do tej pory jesteÅ›my zmuszeni korzystać z procedur, które mogÄ… wykorzystywać/wykonywać polecenia SQL.
Ogólna budowa
Wybaczcie takie dość lakoniczne przedstawienie tego. Niestety sam mam jeszcze pewne problemy z "ogarnięciem" procedur.
CREATE PROCEDURE sp_name ([parameter[,.
[characteristic ...] routine_body
Nowy HelloWorld
No to od razu przedstawiÄ™ jak wyglÄ…da nasza poprzednia funkcja przepisana na procedurÄ™. Warto zauważyć, że nie bÄ™dzie zwracana żadna wartość. WywoÅ‚anie tej procedury również bÄ™dzie inne. Należy również wspomnieć, że procedura nie musi zwracać wartoÅ›ci, ale może (nawet kilka na raz).
DROP PROCEDURE IF EXISTS HelloWorld;
delimiter //
//
CREATE procedure HelloWorld(out jakis_tekst VARCHAR(30))
BEGIN
set jakis_tekst = 'Hello World';
END
//
delimiter ;
Jak wspominaÅ‚em wywoÅ‚anie takiej procedury jest już inne. Ponieważ MySQL nie wie czy wywoÅ‚ana procedura zwróci jakÄ…Å› wartość . Do procedury można przekazać jakÄ…Å› wartość, lub też jÄ… "wyciÄ…gnąć" (in, out). Także aby zobaczyć wynik dziaÅ‚ania procedury trzeba w konsoli wpisać coÅ› takiego:
mysql> CALL HelloWorld(@out);
mysql> Select @out;
+-------------+
| @out |
+-------------+
| Hello World |
+-------------+
1 row in set (0.00 sec)
Select INTO
Możliwe, że źle zrozumiaÅ‚em tekst z kursu, który przeczytaÅ‚em także wybaczcie jeÅ›li siÄ™ pomylÄ™. SELECT INTO sÅ‚uży do tego aby zwrócić wynik zapytania SQL do zmiennej. W tym przykÅ‚adzie wykorzystamy również tabelÄ™, którÄ… stworzyliÅ›my w tym celu.
W przykÅ‚adzie pokażę procedurÄ™, która pobierze jeden rekord z tabeli skroty i zapisze go do zmiennej.
DROP PROCEDURE IF EXISTS pobierz;
delimiter //
CREATE PROCEDURE pobierz (out nazwa VARCHAR(50))
BEGIN
SELECT opis INTO nazwa FROM skroty LIMIT 1;
END
//
delimiter ;
Oraz oczywiÅ›cie sposób wywoÅ‚ania i zwracany wynik:
mysql> CALL pobierz (@out);
Query OK, 0 rows affected (0.05 sec)
mysql> select @out;
+----------------+
| @out |
+----------------+
| lots of laught |
+----------------+
1 row in set (0.01 sec)
Na koniec troszkÄ™ zmodyfikujemy tÄ… procedurÄ™. Nowa procedura bÄ™dzie pobieraÅ‚a jeden parametr (który bÄ™dzie skrótem), natomiast do drugiej zmiennej zostanie zapisany wynik zapytania SQL.
Poza zmiennÄ… out (która bÄ™dzie zwracać wynik), dopiszemy zmiennÄ… in, która bÄ™dzie potrzebna do wykonania zapytania SQL. CaÅ‚y kod nowej procedury wyglÄ…da tak:
DROP PROCEDURE IF EXISTS pobierz;
delimiter //
CREATE PROCEDURE pobierz (out nazwa VARCHAR(50), in skrot VARCHAR(5))
BEGIN
SELECT opis INTO nazwa FROM skroty WHERE skrot_id=skrot LIMIT 1;
END
//
delimiter ;
Oczywiście teraz wywołujemy procedurę:
mysql> CALL pobierz(@out, 'imho');
Query OK, 0 rows affected (0.00 sec)
mysql> select @out;
+----------------------+
| @out |
+----------------------+
| in my humble opinion |
+----------------------+
1 row in set (0.00 sec)
Rekursywne procedury
Tak to jest możliwe, ale trzeba dodatkowo zmienić zimennÄ… max_sp_recursion_depth. Standardowo jest ona ustawiona na 0, co oznacza, że procedura nie może zagÅ‚ebić siÄ™ dalej (czyli na chÅ‚opski rozum, nie bedzie mogÅ‚a ani razu wykonać samej siebie ;-]). Wartość możemy przypisać dowolnÄ…, ale jest to pewnego rodzaju ograniczenie, ponieważ nie do koÅ„ca bÄ™dziemy wiedzieć ile razy procedura bÄ™dzie miaÅ‚a siÄ™ powtórzyć...
Zakończenie
Nie jestem za bardzo utalentowany w pisaniu tego typu kursów, także wybaczcie i poprawiajcie wszelkie błędy. W poznaniu funkcji i procedur bardzo pomógÅ‚ mi artykuÅ‚ znaleziony na Revealnet. Gdyby byÅ‚y jakieÅ› wÄ…tpliwoÅ›c to warto zajrzeć wÅ‚aÅ›nie tam ;-]
P.S. Mam nadzieję, że komuś to się przyda ;-]