如何根据其他记录的值更新字段

Posted

技术标签:

【中文标题】如何根据其他记录的值更新字段【英文标题】:How to update fields based on value of other record 【发布时间】:2016-03-18 10:28:27 【问题描述】:

我有一个类似于以下结构的表:

City        start_date             end_date
Paris       1995-01-01 00:00:00    1997-10-01 23:59:59
Paris       1997-10-02 00:00:00    0001-01-01 00:00:00
Paris       2013-01-25 00:00:00    0001-01-01 00:00:00
Paris       2015-04-25 00:00:00    0001-01-01 00:00:00
Berlin      2014-11-01 00:00:00    0001-01-01 00:00:00
Berlin      2014-06-01 00:00:00    0001-01-01 00:00:00
Berlin      2015-09-11 00:00:00    0001-01-01 00:00:00
Berlin      2015-10-01 00:00:00    0001-01-01 00:00:00
Milan       2001-01-01 00:00:00    0001-01-01 00:00:00
Milan       2005-10-02 00:00:00    2006-10-02 23:59:59
Milan       2006-10-03 00:00:00    2015-04-24 23:59:59
Milan       2015-04-25 00:00:00    0001-01-01 00:00:00

数据包含基于城市的开始和结束日期的历史视图。一个城市的最新记录应该是开始日期最长的记录,结束日期为“0001-01-01 00:00:00”,表示还没有结束日期。

我需要清理这些数据并确保每个城市的历史记录都有结束日期比下一条记录的开始日期早一秒,仅在 end_date 设置为 '0001- 的情况下01-01 00:00:00'。因此,如果 end_date 具有实际日期,则将被忽略。另外,城市的start_date最近的记录也不需要修改end_date。

结果表应如下所示:

City        start_date             end_date
Paris       1995-01-01 00:00:00    1997-10-01 23:59:59
Paris       1997-10-02 00:00:00    2013-01-24 23:59:59
Paris       2013-01-25 00:00:00    2015-04-24 23:59:59
Paris       2015-04-25 00:00:00    0001-01-01 00:00:00
Berlin      2014-11-01 00:00:00    2014-05-31 23:59:59
Berlin      2014-06-01 00:00:00    2015-09-10 23:59:59
Berlin      2015-09-11 00:00:00    2015-09-30 23:59:59
Berlin      2015-10-01 00:00:00    0001-01-01 23:59:59
Milan       2001-01-01 00:00:00    2005-10-01 23:59:59
Milan       2005-10-02 00:00:00    2006-10-02 23:59:59
Milan       2006-10-03 00:00:00    2015-04-24 23:59:59
Milan       2015-04-25 00:00:00    0001-01-01 00:00:00

我想了很多以编程方式实现这一点的方法,但是我希望有一个解决方案可以完全通过 SQL 查询来处理这个问题。我发现了一个类似的问题,答案是here,但是它不能处理我的特定情况。如何修改它以满足我的标准?

编辑:

我已根据以下声明尝试了以下建议的答案:

update test join
       (select t.*,
               (select min(start_date)
                from test t2
                where t2.city = t.city and
                      t2.start_date > t.start_date
                order by t2.start_date
                limit 1
               ) as next_start_date
        from test t
       ) tt
       on tt.city = test.city and tt.start_date = test.start_date
    set test.end_date = date_sub(tt.next_start_date, interval 1 second)
where test.end_date = '0001-01-01' and
      next_start_date is not null;

不幸的是,从柏林记录开始,一些 end_dates 与预期不符(例如 id 编号 5 和 6)。如下所示:

以下是可以复制的创建和插入语句:

CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `city` varchar(50) DEFAULT NULL,
  `start_date` datetime DEFAULT NULL,
  `end_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

INSERT INTO test (city, start_date, end_date) VALUES ('Paris','1995-01-01 00:00:00','1997-10-01 23:59:59');
INSERT INTO test (city, start_date, end_date) VALUES ('Paris','1997-10-02 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Paris','2013-01-25 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Paris','2015-04-25 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Berlin','2014-11-01 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Berlin','2014-06-01 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Berlin','2015-09-11 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Berlin','2015-10-01 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Milan','2001-01-01 00:00:00','0001-01-01 00:00:00');
INSERT INTO test (city, start_date, end_date) VALUES ('Milan','2005-10-02 00:00:00','2006-10-02 23:59:59');
INSERT INTO test (city, start_date, end_date) VALUES ('Milan','2006-10-03 00:00:00','2015-04-24 23:59:59');
INSERT INTO test (city, start_date, end_date) VALUES ('Milan','2015-04-25 00:00:00','0001-01-01 00:00:00');

【问题讨论】:

我建议你不要存储这个“在下一条记录的开始日期前一秒”但是实际的下一条记录的开始日期。进行更新要容易得多,您必须编写的查询将具有相同的难度,最重要的是:不易出错。额外的好处是,如果您想增强数据库级别的一致性,可以添加外键约束。 【参考方案1】:

你只需要lead() 函数,这在 mysql 中是不可用的。在update 中使用变量是有挑战性的,所以这里有一个带有相关子查询的方法。

获取下一个开始日期:

select t.*,
       (select min(start_date)
        from t t2
        where t2.city = t.city and
              t2.start_date > t.start_date
        order by t2.start_date
        limit 1
       ) as next_start_date
from t;

您现在可以在 update 中使用它,使用 join

update t join
       (select t.*,
               (select min(start_date)
                from t t2
                where t2.city = t.city and
                      t2.start_date > t.start_date
                order by t2.start_date
                limit 1
               ) as next_start_date
        from t
       ) tt
       on tt.city = t.city and tt.start_date = t.start_date
    set t.end_date = date_sub(tt.next_start_date, interval 1 second)
where t.end_date = '0001-01-01' and
      t.next_start_date is not null;

【讨论】:

感谢您的帮助。我已经尝试过了,但是结果并不像预期的那样。我已经编辑了问题以说明我的意思。 我添加了一些 SQL,以便您也能够快速复制您的架构。 这帮助我走上了正轨 - 感谢和 +1。

以上是关于如何根据其他记录的值更新字段的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 根据字段的值触发插入和/或更新的记录

如何根据每条记录的连续表单中 ms-access 中的其他值填充文本框上的值

根据另一个表中的值插入和/或更新记录

jQuery X-Editable:根据其他选择字段的值更新选择字段

-- 使用当前(子表)表某字段更新 对应行记录其他字段 (存在主子表关系) left join

-- 使用当前(子表)表某字段更新 对应行记录其他字段 (存在主子表关系) left join