在存储过程中更新和删除

update and delete in a Stored Procedure

我在 mysql 数据库中有一个 table,它包含值、开始日期和结束日期。 这些值会不时更新,并根据结束日期更新。最后一次更新的结束日期始终为 NULL。 例如:

ID   |   Value   |   Start_Date   |   End_Date
 1   |    0.1    |   2015-10-01   |   2015-10-10
 2   |    0.3    |   2015-10-05   |   2015-10-12
 2   |    0.4    |   2015-10-12   |     NULL
 1   |    0.5    |   2015-10-10   |     NULL
 3   |    0.2    |   2015-10-10   |     NULL

现在,假设我插入了一条没有意义的记录(值与以前相同 - 但 "Start_Date" 不同 - 对于 ID=1):

ID   |   Value   |   Start_Date   |   End_Date
 1   |    0.1    |   2015-10-01   |   2015-10-10
 2   |    0.3    |   2015-10-05   |   2015-10-12
 2   |    0.4    |   2015-10-12   |     NULL
 1   |    0.5    |   2015-10-10   |   2015-10-20
 **1   |    0.5    |   2015-10-20   |     NULL**

我想编写一个存储过程来查找那些行并 "fix" 它们。例如,我想在 运行 SP(对于 ID=1)之后看到什么:

ID   |   Value   |   Start_Date   |   End_Date
 1   |    0.1    |   2015-10-01   |   2015-10-10
 1   |    0.5    |   2015-10-10   |     NULL

意思是,我需要删除我插入的新行并将之前行的 "End_Date" 更新为 "NULL"(我有更多字段 - 主键,以便进行比较,所以我可以找到这两行 - 问题是如何删除某一行并更新另一行)

谢谢!

好的,下面的怎么样。鉴于此示例数据:

CREATE TABLE t
    (`ID` int, `Value` decimal(5,2), `Start_Date` date, `End_Date` date)
;

INSERT INTO t
    (`ID`, `Value`, `Start_Date`, `End_Date`)
VALUES
    (1, 0.1, '2015-10-01', '2015-10-10'),
    (2, 0.3, '2015-10-05', '2015-10-12'),
    (2, 0.4, '2015-10-12', NULL),
    (1, 0.5, '2015-10-10', '2015-10-20'),
    (1, 0.5, '2015-10-20', NULL)
;

您现在可以做的是创建一个没有数据的 table 的副本:

CREATE TABLE tmp_t LIKE t;

然后插入 table:

的清理版本
INSERT INTO tmp_t
SELECT MIN(ID), MIN(Value), MIN(Start_Date)
, IF(MIN(IFNULL(End_Date, '1970-01-01')) = '1970-01-01', NULL, MIN(IFNULL(End_Date, '1970-01-01')))
FROM (
    SELECT
    t.*
    , @gn := IF(@prev_value != `Value` OR @prev_id != ID, @gn + 1 , @gn) AS group_number
    , @prev_value := `Value`
    , @prev_id := ID
    FROM
    t
    , (SELECT @prev_value := NULL, @prev_id := NULL, @gn := 0) var_init_subquery
    ORDER BY Start_Date
) sq
GROUP BY group_number;

请注意,也可以这样做

CREATE TABLE tmp_t AS 
SELECT ...

但我选择了上面的版本,因为 CREATE TABLE ... LIKE ... 也像原来的 table 一样创建了主键、索引和外键约束等。 CREATE TABLE ... AS 不会这样做。

总之,接下来你要做的就是:

RENAME TABLE t TO t_backup, tmp_t TO t;

这将很快完成并且也是一个原子操作,因此即使在生产环境中使用也是安全的。

您的结果集将是:

mysql > SELECT * FROM t;
+------+-------+------------+------------+
| ID   | Value | Start_Date | End_Date   |
+------+-------+------------+------------+
|    1 |  0.10 | 2015-10-01 | 2015-10-10 |
|    2 |  0.30 | 2015-10-05 | 2015-10-12 |
|    1 |  0.50 | 2015-10-10 | 2015-10-20 |
|    2 |  0.40 | 2015-10-12 | NULL       |
|    1 |  0.50 | 2015-10-20 | NULL       |
+------+-------+------------+------------+

这是它的工作原理。我们在这里所做的就是扫描整个 table 和...
顺便说一句,这是我一开始使用的简化版本,假设您只专注于一个 ID。保留它只是为了完整性,以防你想玩弄它。还是无视吧。

SELECT MIN(ID), MIN(Value), MIN(Start_Date)
, IF(MIN(IFNULL(End_Date, '1970-01-01')) = '1970-01-01', NULL, MIN(IFNULL(End_Date, '1970-01-01')))
FROM (
    SELECT
    t.*
    , @gn := IF(@prev != `Value`, @gn + 1 , @gn) AS group_number
    , @prev := `Value`
    FROM
    t
    , (SELECT @prev := NULL, @gn := 0) var_init_subquery
    WHERE
    ID = 1
    ORDER BY Start_Date
) sq
GROUP BY group_number;

回到说明。 SELECT 子句逐行处理其中的每一行。所以IF()条件中的变量实际上保存的是它们的初始化值或者上一行的值,因为当前行的值是在IF()函数处理完之后赋值的。所以我们所做的就是不断增加 @gn 变量,除非 Value (糟糕的列名)的值相同并且 ID 相同(并且开始日期是 "the next one"(我的英语很烂))。另请注意,这就是 ORDER BY 非常重要的原因。除非您指定,否则关系数据库中没有顺序,所以不要 "optimize" 它离开。

  • here 您可以阅读有关使用变量的更多信息