Mysql 存储过程:如何处理空结果集

Mysql Stored Procedure: how to handle empty result set

我写了一个过程,其中一条语句没有正确执行:

SELECT thumb_image into v_thumb_image FROM RESTAURANT_IMAGE WHERE 
   RESTAURANT_ID = v_restaurant_id

我调查的原因是,如果在任何时间点结果集为空,过程不会 运行 进一步陈述。

请注意,我是在循环中调用它。

我担心的是,如果任何 v_restaurant_id 结果集为空,我不会停止执行。

完整程序:

-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `populate_restaurant_details`()
BEGIN
 DECLARE v_finished_cuisines, 
         v_finished, 
         v_restaurant_id, 
         v_count_discount
 INT DEFAULT 0;

 DECLARE v_cuisines, 
         v_thumb_image 
 varchar(200) DEFAULT "";

 DECLARE cuisine_title varchar(50) DEFAULT "";
 -- Fetch all restaurant id
 DECLARE restaurant_cursor CURSOR FOR
   SELECT id FROM delhifoodonline.restaurant order by id desc;

 DECLARE CONTINUE HANDLER 
  FOR NOT FOUND SET v_finished = 1;

 OPEN restaurant_cursor;

 get_restaurant: LOOP   

   FETCH restaurant_cursor INTO v_restaurant_id;
   IF v_finished = 1 THEN 
    LEAVE get_restaurant;
   END IF;

  SET v_finished_cuisines =""; 
  SET v_thumb_image = "";
begin
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_thumb_image = NULL;

  SELECT thumb_image into v_thumb_image 
  FROM restaurant_image 
  WHERE restaurant_id = v_restaurant_id
  ORDER BY id
  LIMIT 1;
end;

  SELECT count(*) into v_count_discount FROM restaurant_discount WHERE 
    restaurant_id = v_restaurant_id;

BLOCK2: BEGIN

  DECLARE cuisines_cursor CURSOR FOR 
   SELECT cuisine.title FROM restaurant_cuisine INNER JOIN cuisine 
    ON restaurant_cuisine.cuisine_id = cuisine.id
    WHERE 
    restaurant_cuisine.restaurant_id = v_restaurant_id
    LIMIT 0,5;

  DECLARE CONTINUE HANDLER 
    FOR NOT FOUND SET v_finished_cuisines = 1;
  SET v_cuisines = "";
  OPEN cuisines_cursor;

  get_cuisine: LOOP
   FETCH cuisines_cursor INTO cuisine_title;

   IF v_finished_cuisines = 1 THEN 
    LEAVE get_cuisine;
   END IF;

   SET v_cuisines = CONCAT(cuisine_title,", ",v_cuisines);

   END LOOP get_cuisine;
  CLOSE cuisines_cursor;

END BLOCK2;

  SET v_cuisines = TRIM(BOTH ", " FROM v_cuisines);

  IF v_count_discount > 0 THEN
   SET v_count_discount = 1;
  ELSE
   SET v_count_discount = 0;
  END IF;

  UPDATE restaurant SET 
                        thumb_image = v_thumb_image,
                        cuisines_list = v_cuisines,
                        discount_available = v_count_discount
                   WHERE id= v_restaurant_id;
 END LOOP get_restaurant;

CLOSE restaurant_cursor;

END

来自documentation

NOT FOUND is shorthand for the class of SQLSTATE values that begin with '02'. This is relevant within the context of cursors and is used to control what happens when a cursor reaches the end of a data set. If no more rows are available, a No Data condition occurs with SQLSTATE value '02000'. To detect this condition, you can set up a handler for it (or for a NOT FOUND condition). For an example, see Section 13.6.6, “Cursors”. This condition also occurs for SELECT ... INTO var_list statements that retrieve no rows.

所以你的 select 从 restaurant_image table 也满足 NOT FOUND 状态当它 returns 没有行时,调用定义的处理程序导致离开循环。

一个解决方案是为 select 声明另一个处理程序,方法是将它放在 BEGIN...END 块中:

begin
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_thumb_image = NULL;

  SELECT thumb_image into v_thumb_image 
  FROM restaurant_image 
  WHERE restaurant_id = v_restaurant_id
  ORDER BY id
  LIMIT 1;
end;

毕竟,为什么要使用会很慢的存储过程和游标来做到这一点。您可以通过执行单个语句实现相同的功能:

UPDATE restaurant 
SET thumb_image = (
    SELECT thumb_image 
    FROM restaurant_image 
    WHERE restaurant_id = restaurant.id
    ORDER BY id
    LIMIT 1),
discount_available = IF(EXISTS(
    SELECT 1
    FROM restaurant_discount 
    WHERE restaurant_id = restaurant.id), 1, 0), 
cuisines_list = (
    SELECT group_concat(cuisine.title separator ', ')
    FROM restaurant_cuisine
    INNER JOIN cuisine ON restaurant_cuisine.cuisine_id = cuisine.id
    WHERE restaurant_cuisine.restaurant_id = restaurant.id
    LIMIT 0,5)

或者通过消除每一行的子查询使其更快:

UPDATE restaurant r
LEFT JOIN 
    (SELECT restaurant_id, count(*) AS discount_available
    FROM restaurant_discount 
    GROUP BY restaurant_id) d ON r.id = d.restaurant_id
LEFT JOIN 
    (SELECT restaurant_id, thumb_image 
    FROM restaurant_image r1
    WHERE NOT EXISTS (
        SELECT 1 FROM restaurant_image r2 WHERE r2.restaurant_id = r1.restaurant_id AND r2.id < r1.id
    )) t ON r.id = t.restaurant_id
LEFT JOIN
    (SELECT rc.restaurant_id, SUBSTRING_INDEX(GROUP_CONCAT(c.title SEPARATOR ', '), ',', 5) AS cuisines_list
    FROM restaurant_cuisine rc
    INNER JOIN cuisine c ON rc.cuisine_id = c.id
    GROUP BY rc.restaurant_id
    ) rc ON r.id = rc.restaurant_id
SET r.discount_available = IF(d.discount_available = 0, 0, 1),
r.thumb_image = t.thumb_image,
r.cuisines_list = rc.cuisines_list

分别尝试这些子查询以获得更好的理解。