【问题标题】:How can I add a column to a work table using a new stored procedure如何使用新的存储过程将列添加到工作表
【发布时间】:2015-11-29 22:33:40
【问题描述】:

我正在尝试使用新过程向工作表添加一列,该过程与我用来填充工作表中已有列的过程不同。我不确定这样做是否会更有效和节省资源,或者通过添加到我用来填充表中已经存在的列/值的原始过程。无论如何,这里是添加此列的单独过程的代码。该列称为“starter_rest”,表示为“Game_Date”字段中的日期值与该字段/列中先前值之间的差异。

这是我正在使用的代码,它给了我一个错误。

如果存在,则删除过程 accumulate_starter_rest()

分隔符 $$

CREATE PROCEDURE accumulate_starter_rest()
BEGIN
        DECLARE pit_id VARCHAR(8);
        DECLARE gdate DATE;
        DECLARE prev_date DATE;
        DECLARE seq INT;
        DECLARE strt_rst REAL;
        DECLARE prev_year YEAR(4);
        DECLARE end_of_cursor BOOLEAN;

        DECLARE c1 CURSOR FOR
          SELECT starter_rest
            FROM ip_ER_ERA_subtotal_1
            ORDER BY Starting_Pitcher, Game_Date, Game_Number;

        DECLARE CONTINUE HANDLER FOR NOT FOUND
          SET end_of_cursor := TRUE;

        TRUNCATE TABLE ip_ER_ERA_subtotal_1;
        INSERT INTO ip_ER_ERA_subtotal_1 (starter_rest)
        SELECT starter_rest
            FROM ip_ER_ERA_subtotal_1;

        SET end_of_cursor := FALSE;
        SET prev_year := 0;

        OPEN c1;

        fetch_loop: LOOP
          FETCH c1 INTO pit_id, gdate, prev_date, seq, strt_rst;
          IF end_of_cursor THEN
            LEAVE fetch_loop;
          END IF;
          IF YEAR(gdate) != prev_year THEN
            SET strt_rst := 0;
            SET prev_year := YEAR(gdate);
          END IF;
          SET strt_rst := gdate - prev_date;
          END IF;
          UPDATE TABLE ip_ER_ERA_subtotal_1
          SET starter_rest = strt_rst
              WHERE Starting_Pitcher = pit_id
                AND Game_Date = gdate
                AND Game_Number = seq;
        END LOOP;
        CLOSE c1;
      END

      $$

这是我得到的错误:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF;
          UPDATE TABLE ip_ER_ERA_subtotal_1
          SET starter_rest = str' at line 39

这是表格的屏幕截图,其中包含我正在尝试填充的列已创建并具有“NULL”值。

提前感谢您的帮助。

更新:

ALTER TABLE retrosheet.starting_pitcher_game_log ADD COLUMN starter_rest INT;
UPDATE retrosheet.starting_pitcher_game_log AS b,
retrosheet.game AS g
SELECT @prev as previous,
 Game_Date
FROM retrosheet.starting_pitcher_game_log
SET b. starter_rest = Game_date-@prev
WHERE b.`Game_ID` = g.`GAME_ID`

出现错误:

[ERROR in query 2] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT @prev as previous,
 Game_Date
FROM retrosheet.starting_pitcher_game_log
S' at line 3

提前谢谢你。

更新:这是在新年伊始为 starter_rest 设置 0 值的编辑表:

    DROP PROCEDURE IF EXISTS starting_pitcher_stats_rest_time;

DELIMITER $$

    CREATE PROCEDURE starting_pitcher_stats_rest_time()

      BEGIN
        DECLARE pit_id CHAR(10);
        DECLARE gdate DATE;
        DECLARE seq INT;
        DECLARE prev_date DATE;
        DECLARE rest_days INT;
        DECLARE prev_year YEAR(4);
        DECLARE end_of_cursor BOOLEAN;

        DECLARE no_table CONDITION FOR SQLSTATE '42S02';

        DECLARE c1 CURSOR FOR
          SELECT Starting_Pitcher, Game_Date, Game_Number
            FROM starting_pitcher_stats
            ORDER BY Starting_Pitcher, Game_Date, Game_Number;

        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;

        SET prev_date := 0;
        SET end_of_cursor := FALSE;

        OPEN c1;
        fetch_loop: LOOP
          FETCH c1 INTO pit_id, gdate, seq;
          IF end_of_cursor THEN
            LEAVE fetch_loop;
          END IF;

          IF YEAR(gdate) != prev_year THEN
            SET rest_days := 0;
            SET prev_date := 0;
            SET gdate := 0;
            SET prev_year := YEAR(gdate);
          END IF;

          IF prev_date = 0 THEN
            SET rest_days := 0;
          ELSE
            SET rest_days := DATEDIFF(gdate, prev_date);
          END IF;
          SET prev_date := gdate;

          UPDATE starting_pitcher_stats
            SET starter_rest = rest_days
            WHERE Starting_Pitcher = pit_id
              AND Game_Date = gdate
              AND Game_Number = seq;

        END LOOP;

        CLOSE c1;

      END
    $$

    DELIMITER ;

这是一张表格截图,显示了新赛季开始时首发休息的过高价值。理想情况下,它会重置为 0:

任何帮助都会很棒。顺便说一句,达尔文,谢谢你——你的帮助很棒!

【问题讨论】:

  • 在我看来,您不太可能想使用存储过程来编辑表结构。
  • 好的草莓,你能建议怎么做吗?
  • 我的观点是,编辑表格是一项非常罕见的活动,您可能只能手动完成
  • 好的,我计划将更多变量/字段一一添加到表中,因为我想知道如何从表的其他列中的字段派生它们。然后,我将在 SPSS 和 R 中同时调用其中的许多来对它们进行分析。所以我的想法是尽我所能在前端减少处理时间/资源利用率,以便在我实际进行分析之前访问这些变量。你能分享一下这样做的手动方法是什么吗?提前谢谢你。
  • 我认为你应该再看看规范化 - 如果你仍然认为 RDBMS 是正确的解决方案。

标签: mysql datetime stored-procedures


【解决方案1】:

这个问题及其答案建立在对 How can I combine two procedures in one to populate one table rather than each of the two procedures populating it's own table?How can I add a column that increments on another column in same table? 的答案之上,并且这个问题的答案需要对前两个的答案进行细微的改动,我会在适当的时候注明。

由于“投手休息时间”和“平均得分”计算是相互独立的,因此我建议对每一个都采用单独的程序。但是,由于两个过程的结果经常会一起使用,所以我推荐一个通用的暂存表进行计算,并建议refactoring将该暂存表的创建和填充到第三个过程中:

DELIMITER $$

-- DROP PROCEDURE pitcher_stats_reset $$

CREATE PROCEDURE pitcher_stats_reset()
BEGIN
  DROP TEMPORARY TABLE IF EXISTS pitcher_stats_temp;

  CREATE TEMPORARY TABLE pitcher_stats_temp
  (
    pitcher_id      char(10)    NOT NULL,
    game_date       date        NOT NULL,
    game_seq        int         NOT NULL,
    innings_pitched double      DEFAULT 0.0,
    ip_total        double      DEFAULT 0.0,
    earned_runs     INT         DEFAULT 0,
    er_total        INT         DEFAULT 0,
    std_era         DOUBLE      DEFAULT 0.0,
    starter_rest    INT         DEFAULT 0,
    CONSTRAINT pitcher_stats_temp_pk
      PRIMARY KEY (pitcher_id , game_date , game_seq )
  ) ENGINE=InnoDB;

  INSERT INTO pitcher_stats_temp
        (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;
END $$

DELIMITER ;

以前的版本使用普通的持久表,因为我还不熟悉 MySQL 对临时表的处理。用户注销时会自动删除临时表,回收用于派生数据的空间,这些空间可以在需要时重新生成。根据 MySQL 文档,删除和重新创建表相当于 TRUNCATEing(除了表不需要事先存在),这反过来比无条件的 DELETE 快得多。我也对earned-runs-average procedure 进行了适当的注释更改。

计算投手休息时间的程序再次遵循标准的“控制中断”习语。请注意,我们在进入循环之前读取了第一条记录并设置了一次控制字段,然后在循环中我们测试退出条件,处理“当前”记录,读取“下一条”记录,然后循环。

DROP PROCEDURE IF EXISTS pitcher_stats_rest_time;

DELIMITER $$

CREATE PROCEDURE pitcher_stats_rest_time()
  BEGIN
    DECLARE pit_id          CHAR(10);
    DECLARE prev_pit        CHAR(10);
    DECLARE gdate           DATE;
    DECLARE seq             INT;
    DECLARE prev_date       DATE;
    DECLARE rest_days       INT;
    DECLARE end_of_cursor   BOOLEAN;

    DECLARE no_table CONDITION FOR SQLSTATE '42S02';

    DECLARE c1 CURSOR FOR
      SELECT pitcher_id, game_date, game_seq
        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;

    SET end_of_cursor := FALSE;

    -- Read first record and initialize control fields
    OPEN c1;
    FETCH c1 INTO pit_id, gdate, seq;
    SET prev_date := 0;
    SET prev_pit := pit_id;

    fetch_loop: LOOP
      -- Test for end-of-cursor
      IF end_of_cursor THEN
        LEAVE fetch_loop;
      END IF;

      -- Test for change in control fields. If the pitcher changes,
      --  fake a change in the year to trigger the break.
      IF pit_id != prev_pit THEN
        SET prev_date := 0;
      END IF;

      IF YEAR(prev_date) = YEAR(gdate) THEN
        SET rest_days := DATEDIFF(gdate, prev_date);
      ELSE
        SET rest_days := 0;
      END IF;

      UPDATE pitcher_stats_temp
        SET starter_rest = rest_days
        WHERE pitcher_id = pit_id
          AND game_date = gdate
          AND game_seq = seq;

      -- After processing record, update control fields
      SET prev_date := gdate;
      SET prev_pit := pit_id;

      -- Read next record and repeat
      FETCH c1 INTO pit_id, gdate, seq;
    END LOOP;

    CLOSE c1;

  END $$

DELIMITER ;

在使用中,首先调用pitcher_stats_reset(),初始化工作表。一旦完成,pitcher_stats_era()pitcher_stats_rest_time() 可以按任何顺序重复调用。如果没有先调用pitcher_stats_reset(),其他两个程序会发出礼貌的提醒。

【讨论】:

  • 达尔文,太好了!我喜欢你的想法,即在需要时调用临时表,以及在运行用数据填充临时表的过程之前调用工作表的初始化过程。在 CREATE TEMPORARY TABLE 过程的第二个 IFNULL 语句中将“runs”更改为“earned_runs”后,该过程运行良好。在从 pitcher_stats_era 过程中删除被屏蔽的文本后,其他两个也是如此(因为被屏蔽的文本会出错)。但是,由于某种原因,即使在程序以正确的顺序运行之后,表“pitcher_stats_temp”也不会出现在表列表中。
  • 临时表不显示在SHOW TABLES 下。我担心我不知道如何在 MySQL 中列出临时表,但是如果尚未创建临时表,ERA 和 Pitcher Rest 程序都会礼貌地提醒您。
  • 哇,有没有办法可视化表格的内容?否则如何确保表创建过程创建了正确的内容?甚至还记得已经创建了哪些字段,以免重复已经完成的工作?
  • 您仍然可以DESCRIBE 表格和SELECT 表格。我只是不确定如何列出临时表的名称
  • 嗨,达尔文,为了可视化填充 pitcher_stats 表列的值,我在没有临时表的情况下重复了该过程。我意识到对于“starter_rest”,每个“Starting_Pitcher”的值必须在新赛季(年份)开始时重置为 0,并且每次都有一个新的“Starting_Pitcher”。请参阅上面尝试将代码添加到启动程序休息程序以在新年开始时将值重置为 0。
猜你喜欢
  • 1970-01-01
  • 2016-06-09
  • 2018-02-08
  • 1970-01-01
  • 2017-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-30
相关资源
最近更新 更多