奇怪的MySQL触发行为

时间:2013-11-08 18:35:34

标签: mysql triggers

我使用Raspberry Pi管理我的家庭自动化系统并收集大量数据(温度,水位,能源使用......),主要是每分钟。由于Pi不是很强大,我通过实现缓存表来优化我的Web前端查询,缓存表聚合每小时和每日值来加速一切。我最近的补充是使用触发器自动传播INSERTUPDATE语句。每次将值插入(或更新)到water_level表中时,其触发器将触发并正确计算触发事件发生的小时的开始和结束。此外,minmax值已正确计算并插入water_level_hourly表中。查看表格时,minmax值会按预期显示。

问题从我添加到表water_level_hourly的第二个触发开始。其目的是每次在每小时表中插入或更新某些内容时,将所有小时值聚合为每日值。我从第一个表中复制/粘贴了触发器,但更改了当前日期的计算(这是插入/更新行的时间的转换为DATE)。不知何故,每当触发器触发时,它都无法从每小时表中正确查询minmax的值,并始终将-1插入每日表中。我需要更改什么才能实现自动计算每日值的预期行为?

我在下面添加了一个示例(我知道我可以将触发器合并到一个存储过程中,但是想先找到问题,所以请耐心等待):

CREATE DATABASE IF NOT EXISTS `homebusdata` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `homebusdata`;

-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `water_level` (
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `level` int(11) NOT NULL,
  PRIMARY KEY (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- --------------------------------------------------------
DROP TRIGGER IF EXISTS `trg_water_level_insert`;
DELIMITER //
CREATE TRIGGER `trg_water_level_insert` AFTER INSERT ON `water_level`
 FOR EACH ROW BEGIN
    DECLARE t1, t2 TIMESTAMP;
    DECLARE min, max FLOAT;

    # calculate start and end of current hour
    SET @t1 := DATE_FORMAT(NEW.`time`, '%Y-%m-%d %H:00:00');
    SET @t2 := DATE_ADD(@t1, INTERVAL 1 HOUR);

    # fetch min/max values of current hour
    SELECT MIN(`level`), MAX(`level`) 
    INTO @min, @max
    FROM `water_level`
    WHERE `water_level`.`time` BETWEEN @t1 AND @t2;

    # care for empty values at beginning of each hour
    IF @min IS NULL THEN
        SET @min := 0;
    END IF;
    IF @max IS NULL THEN
        SET @max := 0;
    END IF;

    # write min/max to hourly table
    INSERT INTO `water_level_hourly` (`time`, `min`, `max`) 
    VALUES (@t1, @min, @max) 
    ON DUPLICATE KEY UPDATE `min`=@min, `max`=@max;
END
//
DELIMITER ;

DROP TRIGGER IF EXISTS `trg_water_level_update`;
DELIMITER //
CREATE TRIGGER `trg_water_level_update` AFTER UPDATE ON `water_level`
 FOR EACH ROW BEGIN
    DECLARE t1, t2 TIMESTAMP;
    DECLARE min, max FLOAT;

    # calculate start and end of current hour
    SET @t1 := DATE_FORMAT(NEW.`time`, '%Y-%m-%d %H:00:00');
    SET @t2 := DATE_ADD(@t1, INTERVAL 1 HOUR);

    # fetch min/max values of current hour
    SELECT MIN(`level`), MAX(`level`) 
    INTO @min, @max
    FROM `water_level`
    WHERE `water_level`.`time` BETWEEN @t1 AND @t2;

    # care for empty values at beginning of each hour
    IF @min IS NULL THEN
        SET @min := 0;
    END IF;
    IF @max IS NULL THEN
        SET @max := 0;
    END IF;

    # write min/max to hourly log
    INSERT INTO `water_level_hourly` (`time`, `min`, `max`) 
    VALUES (@t1, @min, @max) 
    ON DUPLICATE KEY UPDATE `min`=@min, `max`=@max;
END
//
DELIMITER ;

-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `water_level_hourly` (
  `time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `min` int(11) NOT NULL,
  `max` int(11) NOT NULL,
  PRIMARY KEY (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Trigger `water_level_hourly`
--
DROP TRIGGER IF EXISTS `trg_water_level_hourly_insert`;
DELIMITER //
CREATE TRIGGER `trg_water_level_hourly_insert` AFTER INSERT ON `water_level_hourly`
 FOR EACH ROW BEGIN
    DECLARE t DATE;
    DECLARE min, max FLOAT;

    # create date value for current day
    SET @t := DATE(NEW.`time`);

    # get min/max value for current day
    SELECT MIN(`min`), MAX(`max`) 
    INTO @min, @max
    FROM `water_level_hourly`
    WHERE DATE(`water_level_hourly`.`time`) = @t;

    # care for empty values at beginning of each day
    IF @min IS NULL THEN
        SET @min := -1;
    END IF;
    IF @max IS NULL THEN
        SET @max := -1;
    END IF;

    # write min/max into daily log
    INSERT INTO `water_level_daily` (`time`, `min`, `max`) 
    VALUES (@t, @min, @max) 
    ON DUPLICATE KEY UPDATE `min`=@min, `max`=@max;
END
//
DELIMITER ;

DROP TRIGGER IF EXISTS `trg_water_level_hourly_update`;
DELIMITER //
CREATE TRIGGER `trg_water_level_hourly_update` AFTER UPDATE ON `water_level_hourly`
 FOR EACH ROW BEGIN
    DECLARE t DATE;
    DECLARE min, max FLOAT;

    # create date value for current day
    SET @t := DATE(NEW.`time`);

    # get min/max value for current day
    SELECT MIN(`min`), MAX(`max`) 
    INTO @min, @max
    FROM `water_level_hourly`
    WHERE DATE(`water_level_hourly`.`time`) = @t;

    # care for empty values at beginning of each day
    IF @min IS NULL THEN
        SET @min := -1;
    END IF;
    IF @max IS NULL THEN
        SET @max := -1;
    END IF;

    # write min/max into daily log
    INSERT INTO `water_level_daily` (`time`, `min`, `max`) 
    VALUES (@t, @min, @max) 
    ON DUPLICATE KEY UPDATE `min`=@min, `max`=@max;
END
//
DELIMITER ;

-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `water_level_daily` (
  `time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `min` int(11) NOT NULL,
  `max` int(11) NOT NULL,
  PRIMARY KEY (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

以下语句应在所有三个表中创建“42”,但仅在前两个表中创建:

USE `homebusdata`;
INSERT INTO `water_level` (`time`, `level`) VALUES (NOW(), 42);

2 个答案:

答案 0 :(得分:1)

经过一番摆弄后,我设法通过将变量minmax重命名为其他内容来实现它。其他变化可能没有必要。

BEGIN
    DECLARE min_, max_ FLOAT;
    DECLARE t DATE DEFAULT NULL;
    SET t = DATE(new.time);
    # get min/max value for current day
    SELECT MIN(`min`), MAX(`max`) 
    FROM `water_level_hourly`
    WHERE DATE(`water_level_hourly`.`time`) = t INTO min_, max_;

    # care for empty values at beginning of each day
    IF min_ IS NULL THEN
        SET min_ := -1;
    END IF;
    IF max_ IS NULL THEN
        SET max_ := -1;
    END IF;

    # write min/max into daily log
    INSERT INTO `water_level_daily` (`time`, `min`, `max`) 
    VALUES (t, min_, max_) 
    ON DUPLICATE KEY UPDATE `min`=VALUES(min), `max`=VALUES(max);
END

不幸的是,我不确定为什么这是一个问题。我想它会被SELECT ... INTO与变量具有相同的字段名称混淆。

答案 1 :(得分:0)

我认为这个行为是由命名变量MIN和MAx引起的,因为MIN和MAx是mysql中agreggate函数的名称。因此,当您在指令中调用MIN()函数时,分辨率堆栈会有一个带有这些名称的标量变量,而不是函数。