How can I combine two procedures in one to populate one table rather than each of the two procedures populating it's own table?

后端 未结 1 1270
迷失自我
迷失自我 2021-01-27 10:53

I created two tables that were each populated with a different procedure in MySQL with Sequel Pro. While each table has the correct information in it after running the respecti

相关标签:
1条回答
  • 2021-01-27 11:37

    Right; let's see what we have here.

    First, the code has to be blocked as follows:

    variable declarations
    cursor declarations
    handler declarations
    everything else
    

    So your DECLARE CURSOR c2 must appear between DECLARE CURSOR c1 and DECLARE CONTINUE HANDLER. Also, you only need one CONTINUE HANDLER because it takes effect from the point of declaration to the end of the procedure.

    Next is the statement

    INSERT INTO ip_ER_subtotal
        SELECT Starting_Pitcher, Game_Date, Game_Number, innings_pitched, 0.0
            FROM starting_pitchers_game_log;
    

    The named columns in the SELECT clause are the columns you're selecting from, not the ones you're inserting into, so they have to be columns in the table starting_pitchers_game_log. Also, since the columns not being copied from starting_pitchers_game_log (that is, ip_total, er_total and era) all have default values, you could use a column list on the INSERT statement, like so:

    INSERT INTO pitcher_stats_temp
        (Starting_Pitcher, Game_Date, Game_Number, innings_pitched, er)
      SELECT pitcher_id, game_date, game_seq, innings_pitched, runs
        FROM starting_pitchers_game_log;
    

    This saves typing, documents which columns you're actually inserting values into and insulates your INSERT statement from the physical order of columns in the source and target tables.

    Next, once you finish the CURSOR c1 loop, don't truncate the table or you'll lose all the work you've just done! TRUNCATE TABLE deletes all rows currently in the table, and is used here to clear out the results of the previous run.

    Finally, the two loops have to have different labels, say fetch_loop_1 and fetch_loop_2. You would also need to reset accum and end_of_cursor before entering the second loop. However, in this case I believe we can do everything in one loop with one cursor, which makes the code simpler and thus easier to maintain.

    Here's the complete procedure:

    DROP PROCEDURE IF EXISTS pitcher_stats_era;
    
    DELIMITER $$
    
    CREATE PROCEDURE pitcher_stats_era()
      BEGIN
        DECLARE pit_id CHAR(10);
        DECLARE gdate DATE;
        DECLARE seq INT;
        DECLARE in_pit REAL;
        DECLARE er INT;
        DECLARE accum_ip REAL;
        DECLARE accum_er INT;
        DECLARE earned_run_avg REAL;
        DECLARE prev_year YEAR(4);
        DECLARE end_of_cursor BOOLEAN;
    
        DECLARE no_table CONDITION FOR SQLSTATE '42S02';
    
        DECLARE c1 CURSOR FOR
          SELECT pitcher_id, game_date, game_seq, innings_pitched, earned_runs
            FROM pitcher_stats_temp
            ORDER BY pitcher_id, game_date, game_seq;
    
        DECLARE CONTINUE HANDLER FOR NOT FOUND
          SET end_of_cursor := TRUE;
    
        DECLARE EXIT HANDLER FOR no_table
        BEGIN
          SIGNAL no_table
            SET MESSAGE_TEXT = "Work table not initialized. Please call pitcher_stats_reset() before continuing",
            MYSQL_ERRNO = 1146;
        END;
    ------------------------------------------------------------------
    -- The following steps are now performed by pitcher_stats_reset()
    ------------------------------------------------------------------
    --  TRUNCATE TABLE ip_subtotal;  -- Clear our work table for a new run
        -- Copy data from main table into work table
    --  INSERT INTO ip_subtotal
    --      (pitcher_id, game_date, game_seq, innings_pitched, earned_runs)
    --    SELECT pitcher_id, game_date, game_seq,
    --        IFNULL(innings_pitched, 0),  -- replace NULL with 0, if
    --        IFNULL(runs, 0)              --   column not initialized
    --      FROM starting_pitchers_game_log;
    ---------------------------------------------------------------------
    
        SET end_of_cursor := FALSE;  -- reset
        SET prev_year := 0;          -- reset control-break
    
        OPEN c1;
    
        fetch_loop: LOOP
          FETCH c1 INTO pit_id, gdate, seq, in_pit, er;
          IF end_of_cursor THEN
            LEAVE fetch_loop;
          END IF;
    
          -- check control-break conditions
          IF YEAR(gdate) != prev_year THEN
            SET accum_ip := 0.0;
            SET accum_er := 0;
            SET prev_year := YEAR(gdate);
          END IF;
    
          SET accum_ip := accum_ip + in_pit;
          SET accum_er := accum_er + er;
          IF accum_er = 0 THEN  -- prevent divide-by-zero
            SET earned_run_avg := 0;
          ELSE
            SET earned_run_avg := (accum_ip / accum_er) * 9;
          END IF;
    
          UPDATE pitcher_stats_temp
            SET ip_total = accum_ip,
                er_total = accum_er,
                std_era = earned_run_avg
            WHERE pitcher_id = pit_id
              AND game_date = gdate
              AND game_seq = seq;
    
        END LOOP;
    
        CLOSE c1;
      END
    $$
    DELIMITER ;
    

    That should do the job. If anyone finds a bug, by all means please point it out.

    EDIT: I've just added some code to illustrate how to protect against nulls coming from the source table, and how to avoid a divide-by-zero on the ERA calculation.

    EDIT: I've changed back to my original column and table names in order to reduce my own confusion.

    EDIT: Code changed to be consistent with the answer to How can I add a column to a work table using a new stored procedure

    0 讨论(0)
提交回复
热议问题