* Логически оператори и цикли

Публикувано на 19 април 2009 от Филип Петров. Записано в DB.


Чрез процедурите MySQL много наподобява завършен език за програмиране. За това силно спомагат възможностите за логически оператори и цикли. Ще ги разгледаме поотделно:

1. IF-ELSE:

Операторите IF-ELSE имат следната структура:

IF <условие>
   THEN <заявки>;
   ELSE <заявки>;
END IF;

Нека демонстрираме с един пример – процедура, на която подаваме параметри сума и номер на акаунт. Процедурата връща резултат „1″ ако в акаунта има повече пари от посочените или „0″ в противен случай:

mysql> DELIMITER |

mysql> CREATE PROCEDURE check_availability(IN acc INT, IN money DOUBLE)
       BEGIN

              DECLARE acc_avail INT;

              SELECT amount
              INTO acc_avail
              FROM accounts
              WHERE id = acc;

              IF (acc_avail >= money)
                     THEN SELECT 1;
                     ELSE SELECT 0;
              END IF;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

mysql> SELECT id, amount FROM accounts WHERE id = 5;
+----+--------+
| id | amount |
+----+--------+
|  5 | 191.98 |
+----+--------+
1 row in set (0.00 sec)

mysql> CALL check_availability(5, 200);
+---+
| 0 |
+---+
| 0 |
+---+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)

mysql> CALL check_availability(5, 150);
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)

Виждате, че се получи точно желания резултат – в акаунт №5 има точно 191.98. Когато попитаме процедурата дали има 200 тя връща резултат 0, а когато я попитаме дали има 150 връща резултат 1.

Искаме да обърнем внимание на заявката „DECLARE x INT“. Чрез нея ние дефинираме т.нар. локална променлива за процедурата. Тя е валидна само вътре в процедурата и се изтрива след нейното приключване. Виждате, че в случая я инициализирахме като резултат от временна таблица (SELECT -> INTO).

Трябва много да внимавате при евентуално подаване на NULL стойности. Както и при обикновените заявки, всяко сравнение с NULL стойност връща резултат FALSE.

2. CASE:

CASE е аналог на операторът за многовариантен избор switch в езика за програмиране C. Синтаксисът е следния:

CASE <променлива>
   WHEN <условие>
      THEN <заявки>;
   WHEN <условие>
      THEN <заявки>;
   ...
   ELSE <заявки>
END CASE;

Частта ELSE се достига тогава, когато нито едно от условията по-горе не е изпълнено.

Нека демонстрираме с пример – процедура, която по зададен номер на акаунт връща името на типа му:

mysql> DELIMITER |
mysql>
mysql> CREATE PROCEDURE acc_type(IN acc INT)
       BEGIN

              DECLARE acc_t TINYINT;

              SELECT type
              INTO acc_t
              FROM accounts
              WHERE id = acc;

              CASE acc_t
              WHEN 1 THEN
                     SELECT "3 months deposit";
              WHEN 2 THEN
                     SELECT "Annual deposit";
              ELSE
                     SELECT "Unknown account";
              END CASE;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

mysql> SELECT id, type FROM accounts WHERE id = 6 OR id = 7;
+----+------+
| id | type |
+----+------+
|  6 |    2 |
|  7 |    1 |
+----+------+
2 rows in set (0.00 sec)

mysql> CALL acc_type(6);
+----------------+
| Annual deposit |
+----------------+
| Annual deposit |
+----------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)

mysql> CALL acc_type(7);
+------------------+
| 3 months deposit |
+------------------+
| 3 months deposit |
+------------------+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)

3. WHILE:

Процедурите в MySQL добиват още по-голяма сила с наличието на цикли. Първият и може би най-популярен е WHILE:

WHILE <условие>
DO
   <заявки>;
END WHILE;

Например следната процедура ще изведе сумите по акаунти на всички клиенти в зададен диапазон (x,y):

mysql> DELIMITER |

mysql> CREATE PROCEDURE check_clients(IN x INT, IN y INT)
       BEGIN

              DECLARE iterator INT;

              SET iterator = x;

              WHILE (iterator >= x AND iterator <= y)
              DO
                     SELECT id, amount
                     FROM accounts
                     WHERE id = iterator;

                     SET iterator = iterator + 1;
              END WHILE;

       END
       |
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER ;

mysql> CALL check_clients(5,9);

+----+--------+
| id | amount |
+----+--------+
|  5 | 191.98 |
+----+--------+
1 row in set (0.00 sec)

+----+---------+
| id | amount  |
+----+---------+
|  6 | 1220.00 |
+----+---------+
1 row in set (0.00 sec)

+----+--------+
| id | amount |
+----+--------+
|  7 | 133.48 |
+----+--------+
1 row in set (0.00 sec)

+----+--------+
| id | amount |
+----+--------+
|  8 | 256.41 |
+----+--------+
1 row in set (0.00 sec)

+----+---------+
| id | amount  |
+----+---------+
|  9 | 1331.50 |
+----+---------+
1 row in set (0.02 sec)

Query OK, 0 rows affected (0.02 sec)

4. REPEAT-UNTIL:

Цикълът е с абсолютно същото действие както WHILE, с изключение, че ако условието е погрешно в самото начало, то въпреки това тялото на цикъла ще се изпълни поне веднъж:

REPEAT
   <заявки>;
UNTIL <условие>;
END REPEAT;

Този вид цикъл е малко популярен и рядко намира приложение. Затова няма да го разглеждаме подробно с пример.

Задача: Преработете процедурата за прехвърляне на пари от един акаунт в друг като направите така, че ако първият акаунт няма достатъчно пари, то да се изведе съобщение за грешка.



3 коментара за “Логически оператори и цикли”

 
  1. Тодор написа:

    При написаната по-горе процедура check_clients:

    mysql> CALL check_clients(5,6);

    +—-+——–+
    | id | amount |
    +—-+——–+
    | 5 | 191.98 |
    +—-+——–+
    1 row in set (0.00 sec)

    +—-+———+
    | id | amount |
    +—-+———+
    | 6 | 1220.00 |
    +—-+———+
    1 row in set (0.00 sec)

    Процедурата връща 2 „сет“-а с по 1 резултат;
    Въпроси:
    1) Възможно ли е в MySql да се върнат 2-та резултата като 1 сет? Например:
    +—-+——–+
    | id | amount |
    +—-+——–+
    | 5 | 191.98 |
    +—-+——–+
    | 6 | 1220.0 |
    +—-+——–+

    2) Възможно ли е по някакъв начин да се извика процедурата и в същото време да се направи нещо по колекцията, която е върнала?
    Например:

    select *
    from check_clients(5,6)
    where (some condition)

     
  2. Филип Петров написа:

    1) Да, разбира се. Ето например как може да стане с използването на временна таблица (temporary table):

    DELIMITER |
    
    CREATE PROCEDURE check_clients(IN x INT, IN y INT)
    BEGIN
    	DECLARE iterator INT;
    	SET iterator = x;
    
    	CREATE TEMPORARY TABLE result_table (
    		id INT(11),
    		amount decimal(9,2)
    	);
    
    	WHILE (iterator >= x AND iterator < = y)
            DO
    		INSERT INTO result_table(id, amount)
    		SELECT id, amount FROM accounts WHERE id = iterator;
    
                    SET iterator = iterator + 1;
    	END WHILE;
    
    	SELECT * FROM result_table;
    	DROP TABLE result_table;
    END
    |
    
    DELIMITER ;

    2) Не, не можеш да изпълняваш заявка върху resultset от съхранена процедура. Вместо това можеш да накараш процедурата да съхранява информацията във временна таблица, вместо да връща resultset. Така след изпълнението на процедурата можеш да изпълниш заявката върху тази временна таблица и накрая да я изтриеш.

     
  3. Тодор написа:

    Ясно. Мерси :)

     

Trackback URI | RSS за коментарите

Пусни коментар