Μια stored procedure (αποθηκευμένη διαδικασία) είναι ένα τμήμα προγράμματος που αποθηκεύεται στον κατάλογο της Βάσης Δεδομένων και στη συνέχεια μπορεί να κληθεί από ένα πρόγραμμα ή μια άλλη stored procedure.
Τα πλεονεκτήματα μίας stored procedure είναι τα εξής:
Τα μειονεκτήματα των stored procedures είναι τα εξής:
Θα δημιουργήσουμε μια απλή stored procedure και θα την καλέσουμε από τη γραμμή εντολών της MySQL. Ας δούμε ένα παράδειγμα πηγαίου κώδικα μιας stored procedure. Στον παρακάτω κώδικα και σε όλα τα παραδείγματα που ακολουθούν χρησιμοποιούμε έναν πίνακα ταινιών MOVIES με γνωρίσματα το id της ταινίας (mid), τον τίτλο της ταινίας (title) και το έτος παραγωγής της ταινίας (year).
DELIMITER //
CREATE PROCEDURE GetAllMovies()
BEGIN
SELECT * FROM MOVIES;
END //
DELIMITER ;
Η πρώτη εντολή που βλέπετε είναι η εντολή DELIMITER //. Η εντολή αυτή δε σχετίζεται με τη stored procedure. Η εντολή DELIMITER στη MySQL χρησιμοποιείται για να αλλάξουμε το βασικό delimiter της MySQL που είναι το ελληνικό ερωτηματικό (;) σε κάποιον άλλο. Σε αυτή την περίπτωση ο delimiter αλλάζει από ελληνικό ερωτηματικό (;) σε //, ώστε να μπορούμε να έχουμε πολλαπλές SQL εντολές σε μια stored procedure που να διαχωρίζονται με το ελληνικό ερωτηματικό (;).
Για να δημιουργήσουμε μια νέα stored procedure χρησιμοποιούμε την εντολή CREATE PROCEDURE. Μετά την εντολή CREATE PROCEDURE γράφουμε το όνομα της stored procedure, το οποίο στην περίπτωσή μας είναι το GetAllMovies. To κυρίως τμήμα της stored procedure είναι ένα block που αρχίζει με την εντολή BEGIN και τελειώνει με την εντολή END. Σε αυτό το τμήμα μπορούμε να γράψουμε SQL εντολές. Θα δώσουμε περισσότερες λεπτομέρειες για αυτά στη συνέχεια. Μετά την εντολή END χρησιμοποιούμε το delimiter // για να δείξουμε το τέλος της stored procedure.
ΠΡΟΣΟΧΗ! Μετά την εντολή END της stored procedure θα πρέπει οπωσδήποτε να αλλάξoυμε πάλι τον delimiter στο ελληνικό ερωτηματικό (;) με χρήση της εντολής DELIMITER ;
Μέχρι τώρα έχουμε δημιουργήσει μια νέα stored procedure και στη συνέχεια θα δούμε πώς μπορούμε να την καλέσουμε από τη γραμμή εντολών της MySQL ή από μέσα από ένα άλλο πρόγραμμα.
Για να καλέσουμε μια stored procedure χρησιμοποιούμε την SQL εντολή:
CALL STORED_PROCEDURE_NAME();
Για παράδειγμα, μπορούμε να καλέσουμε τη stored procedure που δημιουργήσαμε πιο πάνω από τη γραμμή εντολών της MySQL με τον εξής τρόπο:
CALL GetAllMovies();
Η κλήση της stored procedure δίνει ως αποτέλεσμα όλες τις ταινίες που υπάρχουν στον πίνακα MOVIES της βάσης.
Για να διαγράψουμε μια stored procedure χρησιμοποιούμε την SQL εντολή:
DROP PROCEDURE STORED_PROCEDURE_NAME;
Για παράδειγμα για να διαγράψουμε τη stored procedure GetAllMovies που δημιουργήσαμε γράφουμε το εξής στη γραμμή εντολών της MySQL:
DROP PROCEDURE GetAllMovies;
Οι τοπικές μεταβλητές είναι οι μεταβλητές που χρησιμοποιούνται σε μια stored procedure για την αποθήκευση του τρέχοντος αποτελέσματος. Μπορούμε να δηλώσουμε μια τοπική μεταβλητή στην stored procedure με την εξής εντολή:
DECLARE variable_name datatype(size) DEFAULT default_value;
Το όνομα της μεταβλητής ακολουθεί την εντολή DECLARE. Το όνομα μιας μεταβλητής θα πρέπει να ακολουθεί τους κανόνες ονοματολογίας της MySQL και δε θα πρέπει να είναι ίδιο με το όνομα πίνακα ή στήλης (γνωρίσματος). Στη συνέχεια δίνουμε τον τύπο δεδομένων της μεταβλητής, που μπορεί να είναι οποιοσδήποτε τύπος υποστηρίζεται από τη MySQL όπως INT, VARCHAR ή DATETIME κτλ. Όταν δηλώνουμε μια μεταβλητή η αρχική της τιμή είναι NULL. Μπορούμε, επίσης, να δώσουμε μια default τιμή για μια μεταβλητή χρησιμοποιώντας την εντολή DEFAULT.
Για παράδειγμα, αν θέλουμε να δηλώσουμε μια μεταβλητή με όνομα total_movies τύπου INT και default τιμή 0, τότε έχουμε:
DECLARE total_movies INT DEFAULT 0;
Για να δηλώσουμε δύο ή περισσότερες μεταβλητές με τον ίδιο τύπο δεδομένων μπορούμε να χρησιμοποιήσουμε μία εντολή DECLARE ως εξής:
DECLARE x, y INT DEFAULT 0;
Μετά τη δήλωση μιας μεταβλητής μπορούμε να τη χρησιμοποιήσουμε. Για να αναθέσουμε τιμή σε μια μεταβλητή χρησιμοποιούμε την εντολή SET. Για παράδειγμα:
DECLARE total_movies INT DEFAULT 0;
SET total_movies = 100;
Εκτός από την εντολή SET μπορούμε να χρησιμοποιήσουμε την εντολή SELECT-FROM-WHERE ... INTO για να αναθέσουμε το αποτέλεσμα ενός SQL ερωτήματος σε μια μεταβλητή. Για παράδειγμα:
DECLARE total_movies INT DEFAULT 0;
SELECT COUNT(*) INTO total_movies
FROM MOVIES;
Στο προηγούμενο παράδειγμα, αρχικά δηλώσαμε τη μεταβλητή total_movies που είναι τύπου INT και την αρχικοποιήσαμε με την τιμή μηδέν. Στη συνέχεια, με την εντολή SELECT ... INTO αναθέσαμε στη μεταβλητή total_movies το αποτέλεσμα της εντολής SELECT, δηλαδή το πλήθος των πλειάδων του πίνακα MOVIES.
Μετά την εκτέλεση μιας stored procedure οι τοπικές μεταβλητές αποδεσμεύουν τη μνήμη που έχουν καταλάβει και οι τιμές που είχαν δε διατηρούνται. Για να διατηρήσουμε τις τιμές μεταβλητών που υπολογίστηκαν σε μια stored procedure μετά το τέλος της χρησιμοποιούμε μεταβλητές χρήστη. Μια μεταβλητή χρήστη στη MySQL είναι μια μεταβλητή με καθολική εμβέλεια αντίστοιχη μιας global μεταβλητής σε μια διαδικαστική γλώσσα προγραμματισμού όπως οι C, Java κ.λπ.
Μπορούμε να ορίσουμε και ταυτόχρονα να αρχικοποιήσουμε μια μεταβλητή χρήστη στη γραμμή εντολών της MySQL χρησιμοποιώντας την εντολή SET και το σύμβολο @ χωρίς χρήση τύπου μεταβλητής ως εξής:
SET @variable_name = variable_value;
Για παράδειγμα για να αναθέσουμε στη μεταβλητή a το αλφαριθμητικό 'ABC' κάνουμε το εξής:
SET @a = 'ABC';
Για να εμφανίσουμε την τιμή μιας μεταβλητής χρήστη γράφουμε στη γραμμή εντολών της MySQL το εξής:
SELECT @variable_name;
Για παράδειγμα για να εμφανίσουμε την τιμή της μεταβλητής a δίνουμε την εντολή:
SELECT @a;
Στη συνέχεια θα μάθουμε πώς να δημιουργούμε stored procedures με παραμέτρους. Σχεδόν όλες οι stored procedures που θα αναπτύξετε χρειάζονται παραμέτρους. Οι παράμετροι κάνουν τη stored procedure πιο ευέλικτη και χρήσιμη.
Στη MySQL μια παράμετρος μπορεί να είναι τύπου IN, OUT ή INOUT.
Η σύνταξη για να ορίσουμε μια παράμετρο σε μια stored procedure έχει ως εξής:
MODE param_name param_type(param_size)
MODE είναι ο τύπος παραμέτρου, δηλαδή μπορεί να είναι IN, OUT ή INOUT.
param_name είναι το όνομα της παραμέτρου. Το όνομα θα πρέπει να ακολουθεί τους κανόνες ονοματολογίας της MySQL και να μην είναι ίδιο με το όνομα των στηλών των πινάκων.
param_type είναι ο τύπος δεδομένων της παραμέτρου, δηλαδή μπορεί να είναι INT, VARCHAR κ.λπ.
param_size είναι το μέγεθος της παραμέτρου.
Ας δούμε τη χρήση παραμέτρων στις stored procedures με ένα παράδειγμα στο οποίο παίρνουμε ως αποτέλεσμα όλες τις ταινίες κάποιου έτους.
DELIMITER //
CREATE PROCEDURE GetMoviesByYear (IN inputYear INT)
BEGIN
SELECT mid, title
FROM MOVIES
WHERE year = inputYear;
END //
DELIMITER ;
Όπως μπορείτε να δείτε χρησιμοποιήσαμε ως ΙΝ παράμετρο το inputYear με τύπο δεδομένων INT. Στο block της stored procedure που βρίσκεται ανάμεσα στις εντολές BEGIN και END, γράψαμε μια SQL ερώτηση για να ανακτήσουμε όλες τις ταινίες οι οποίες δημιουργήθηκαν το έτος inputYear.
Ας υποθέσουμε ότι θέλουμε να ανακτήσουμε όλες τις ταινίες που δημιουργήθηκαν το έτος 2010. Τότε απλώς περνάμε ως παράμετρο το έτος 2010 στη stored procedure και την καλούμε ως εξής:
CALL GetMoviesByYear(2010);
Για να ανακτήσουμε όλες τις ταινίες που δημιουργήθηκαν το έτος 1987 κάνουμε το εξής:
CALL GetMoviesByYear(1987);
Στις stored procedures o χρήστης μπορεί να ελέγχει τη ροή του προγράμματος, με τρόπο παρόμοιο με τις κλασσικές διαδικαστικές γλώσσες προγραμματισμού.
Μια εντολή IF συντάσσεται ως εξής:
IF <condition_1> THEN ...
ELSEIF <condition_2> THEN ...
... ...
ELSEIF <condition_3> THEN ...
ELSE ...
END IF;
Στο παρακάτω παράδειγμα φαίνεται η χρήση της IF. Στο παράδειγμα αυτό αναλόγως με την τιμή της μεταβλητής myYear εκτελείται το κατάληλο SELECT ώστε να δούμε κάποια δεδομένα. Υποθέτουμε ότι στη βάση έχουμε ταινίες που έχουν δημιουργηθεί τα έτη από 1930 έως και 2010.
DELIMITER //
CREATE PROCEDURE ExampleIf(ΙΝ myYear INT)
BEGIN
IF myYear >= 1930 && myYear <= 2010 THEN
SELECT * FROM MOVIES WHERE year = myYear;
ELSEIF myYear < 1930 THEN
SELECT * FROM MOVIES WHERE year BETWEEN 1930 AND 1940;
ELSEIF myYear > 2010 THEN SELECT * FROM MOVIES WHERE year BETWEEN 2009 AND 2010;
END IF;
END //
DELIMITER ;
Οι επαναλήψεις (loops) γίνονται με διάφορους τρόπους. Ας δούμε τη χρήση της εντολής WHILE.
Μια εντολή WHILE συντάσσεται ως εξής:
WHILE <condition> DO
<loop_body>
END WHILE
Ας δούμε τη χρήση της εντολής WHILE με ένα παράδειγμα.
DELIMITER //
CREATE PROCEDURE WhileProc (IN myYear INT, IN offset INT)
BEGIN
DECLARE lastYear INT;
SET lastYear = myYear + offset;
WHILE myYear <= lastYear DO
SELECT * FROM MOVIES WHERE year = myYear;
SET myYear = myYear + 1;
END WHILE;
END //
DELIMITER ;
Στο παραπάνω παράδειγμα εμφανίσαμε επαναληπτικά τα περιεχόμενα του πίνακα MOVIES για ταινίες που έχουν δημιουργηθεί τα έτη από myYear έως και myYear + offset.
Η εντολή LOOP συντάσσεται ως εξής:
[begin_label:] LOOP
<loop_body>
END LOOP [end_label];
Με την εντολή ITERATE στο loop_body η επανάληψη συνεχίζεται, ενώ με την εντολή LEAVE η επανάληψη τερματίζει.
Ας δούμε ένα παράδειγμα όπου θα τροποποίησουμε τη stored procedure WhileProc και θα αντικαταστήσουμε την εντολή WHILE με την ισοδύναμη εντολή LOOP, LEAVE και ΙΤΕRΑΤΕ.
DELIMITER //
CREATE PROCEDURE LoopProc (IN myYear INT, IN offset INT)
BEGIN
DECLARE lastYear INT;
SET lastYear = myYear + offset;
myLabel: LOOP
SELECT * FROM MOVIES WHERE year = myYear;
IF myYear = lastYear THEN
LEAVE myLabel;
END IF;
SET myYear = myYear + 1;
ITERATE myLabel;
END LOOP myLabel;
END //
DELIMITER ;
Άλλος τρόπος για να κάνουμε επαναλήψεις είναι η εντολή:
Ο cursor είναι μια μεταβλητή με την οποία μπορούμε να διατρέχουμε τις πλειάδες (γραμμές) κάποιας σχέσης. Η σχέση αυτή μπορεί να είναι ένας αποθηκευμένος πίνακας ή το αποτέλεσμα μιας ερώτησης. Με το να αναθέτουμε πλειάδες στην δομή cursor, είναι δυνατό να φτιάχνουμε προγράμματα που να διαβάζουν και να επεξεργάζονται τις τιμές κάθε στοιχείου αυτών των πλειάδων. Εάν η σχέση είναι αποθηκευμένη (πίνακας), μπορούμε να αλλάζουμε τις τιμές αυτές ή να διαγράφουμε τελείως τις πλειάδες.
Η MySQL υποστηρίζει τη χρήση cursors στις stored procedures. Αρχικά θα πρέπει να δηλώσουμε τη χρήση cursor με την εντολή DECLARE.
DECLARE cursor_name CURSOR FOR SELECT_Statement;
Στη συνέχεια θα πρέπει να ανοίξουμε τον cursor με την εντολή OPEN. Θα πρέπει οπωσδήποτε να ανοίξουμε τον cursor για να αρχίζουμε να διαβάσουμε γραμμές από αυτόν (δηλαδή πριν την εντολή FETCH).
OPEN cursor_name;
Έπειτα μπορούμε να ανακτήσουμε την τρέχουσα γραμμή που έχει διαβαστεί από τον cursor και να μετακινήσουμε τον cursor στην επόμενη γραμμή με την εντολή FETCH.
FETCH cursor_name INTO variable_list;
ΠΡΟΣΟΧΗ! Οι μεταβλητές στη variable_list της εντολής FETCH θα πρέπει να έχουν ένα προς ένα αντιστοιχία με τα γνωρίσματα του SELECT στη δήλωση του cursor.
Για παράδειγμα αν δηλώσουμε έναν cursor με την εντολή:
DECLARE myCursor CURSOR FOR SELECT * FROM MOVIES;
H εντολή FETCH πρέπει να είναι:
FETCH myCursor INTO A, B, C;
Με το Α να αντιστοιχεί στο γνώρισμα mid του πίνακα MOVIES, το Β στο γνώρισμα title του πίνακα MOVIES και το C στο γνώρισμα year του πίνακα MOVIES. Ο τύπος δεδομένων κάθε μεταβλητής στην εντολή FETCH θα πρέπει να είναι ίδιος με τον τύπο δεδομένων του γνωρίσματος στο οποίο αντιστοιχεί.
Τέλος, όταν ολοκληρώσουμε αυτό που κάνουμε και δε χρειαζόμαστε περισσότερο τον cursor που δημιουργήσαμε, θα πρέπει να τον απενεργοποιήσουμε ώστε να αποδεσμεύσουμε τη μνήμη που καταλαμβάνει. Αυτό το κάνουμε με την εντολή CLOSE.
CLOSE cursor_name;
Ένα από τα πιο σημαντικά σημεία όταν δουλεύουμε με cursors είναι η χρήση της εντολής ΝΟΤ FOUND για την αποφυγή λάθους αν δεν υπάρχει άλλη εγγραφή να γίνει FETCH από τον cursor. Αυτό γίνεται χρησιμοποιώντας την εντολή:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET variable_name = value;
Η εντολή αυτή δηλώνει ότι αν δεν υπάρχει άλλη εγγραφή για να κάνει FETCH ο cursor, τότε η μεταβλητή με όνομα variable_name παίρνει την τιμή value.
ΠΡΟΣΟΧΗ! Η παραπάνω εντολή όταν χρησιμοποιείται θα πρέπει να δίνεται μετά τη δήλωση του cursor.
Στη συνέχεια παραθέτουμε ένα παράδειγμα χρήσης cursor στη stored procedure ReverseMovieNames. H stored procedure διαβάζει τις maxTuples πρώτες εγγραφές του πίνακα MOVIES και αντιστρέφει τη σειρά των γραμμάτων των τίτλων τους. Στον κώδικα της stored procedure υπάρχουν επεξηγηματικά σχόλια για το κάθε βήμα.
DELIMITER //
/* Αν υπάρχει stored procedure με το ίδιο όνομα τη διαγράφουμε.*/
DROP PROCEDURE IF EXISTS ReverseMovieNames$$
/*Δημιουργούμε μια νέα stored procedure.*/
CREATE PROCEDURE ReverseMovieNames(IN maxTuples INT)
BEGIN
/*Δήλωση μεταβλητών.*/
DECLARE counter INT DEFAULT 0;
DECLARE done BOOL DEFAULT FALSE;
DECLARE movieId INT DEFAULT 0;
DECLARE movieName VARCHAR(50) DEFAULT "";
/*Δήλωση του cursor.*/
DECLARE cur_movie CURSOR FOR
SELECT mid, title FROM MOVIES;
/*Σε περίπτωση που δεν υπάρχει άλλη εγγραφή για να κάνει FETCH ο cursor
η μεταβλητή done παίρνει την τιμή TRUE.*/
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
/*Ανοίγουμε τον cursor.*/
OPEN cur_movie;
myLabel: LOOP
/*Διαβάζουμε την τρέχουσα γραμμή του cursor, αποθηκεύουμε τα δεδομένα
στις αντίστοιχες μεταβλητές και μετακινούμε τον cursor στην επόμενη εγγραφή.*/
FETCH cur_movie INTO movieId, movieName;
/*Αν στον πίνακα MOVIES υπάρχουν λιγότερες από maxTuples εγγραφές ή
ο cursor έχει κάνει FETCH περισσότερες από maxTuples εγγραφές η επανάληψη
τερματίζει.*/
IF (done OR counter >= maxTuples) THEN
LEAVE myLabel;
END IF;
/*Αντιστρέφουμε τη σειρά των γραμμάτων του τίτλου της ταινίας.*/
SET movieName = REVERSE(movieName);
/*Ενημερώνουμε την εγγραφή του πίνακα MOVIES για το νέο τίτλο ταινίας.*/
UPDATE MOVIES SET title = movieName WHERE mid = movieId;
SET counter = counter + 1;
ITERATE myLabel;
END LOOP myLabel;
/*Κλείνουμε τον cursor.*/
CLOSE cur_movie;
END //
DELIMITER ;
Εάν θέλετε να τυπώσετε κάποιο μήνυμα μέσα στην stored procedure σας μπορείτε να χρησιμοποιήσετε μία εντολή SELECT όπως φαίνεται παρακάτω. Στην δεύτερη περίπτωση, οι var1 και var2 είναι μεταβλητές και γίνεται η χρήση της συνάρτησης CONCAT της MySQL η οποία συνενώνει τις τιμές των μεταβλητών με το κείμενό σας.
SELECT 'my Text';
SELECT CONCAT('Some text', var1,' more text', var2);
Για περισσότερες πληροφορίες σχετικά με τις MySQL stored procedures μπορείτε να δείτε το εξής: MySQL stored procedure manual.