【问题标题】:MySQL - Find start and end of blocks of consecutive rows with the same valueMySQL - 查找具有相同值的连续行块的开始和结束
【发布时间】:2021-01-22 07:55:41
【问题描述】:

我需要从一个表中提取值并将其迁移到另一个表。源表包含特定生效日期的汇总值。如果值发生更改,如果组件值发生更改,并且数据从该生效日期开始有效,则会写入新行。

source_id entity_id effective_date component_1 component_2 component_3
int(ai) int date int int int
1 159 2020-01-01 100 0 90
2 159 2020-05-01 140 50 90
3 159 2020-08-01 0 30 90
5 159 2020-12-01 0 30 50

我现在需要将此数据迁移到这样的新表中。目标是选择给定月份的数据,结果是给出该月的有效数据。

id source_id entity_id startdate enddate component_type value
int(ai) int int date date int int

每一行代表一个在一个月内有效的组件的值。 我现在通过将其设置为参数来运行每个有效月份的插入更新。 我将值更改作为新行插入到表中,并使用唯一键(实体 ID、有效日期、组件类型)防止重复

    SET @effective_date = '2020-01-01';
    INSERT INTO component_final 
       select NULL,
              source_id, 
              entity_id,
              effective_date,
              NULL,
              1,
              component_1 
       FROM component_source 
       WHERE effective_date = @effective_date 
       AND component_1>0;

迁移第一行后应该是那个结果

id source_id entity_id startdate enddate component_type value
1 1 159 2020-01-01 NULL 1 100
2 1 159 2020-01-01 NULL 3 90
    SET @effective_date = '2020-05-01';
    INSERT INTO component_final 
       select NULL,
              source_id, 
              entity_id,
              effective_date,
              NULL,
              1,
              component_1 
       FROM component_source 
       WHERE effective_date = @effective_date 
       AND component_1>0;

迁移第二行后应该是那个结果

id source_id entity_id startdate enddate component_type value
1 1 159 2020-01-01 2020-04-30 1 100
2 1 159 2020-01-01 NULL 3 90
3 2 159 2020-05-01 NULL 1 140
4 2 159 2020-05-01 NULL 2 50

因此,如果将来值发生变化,则必须设置结束日期。

如果将来更改组件,我将无法执行第二步,即更新数据。 也许可以在插入具有相同实体和组件的新行后将其作为触发器 - 但我无法使其工作。

一些想法?我只想在MySQL 内部处理这个问题。

【问题讨论】:

  • 你的 MySQL 版本是多少?
  • 您不能在一个查询中同时插入新行和更新现有行。在存储过程中执行您的操作。或者使用 2 个查询。
  • 为什么要逐行迁移,而不是在一个查询中迁移整个数据数组?
  • @Akina 如果我知道该怎么做 - 我会的。上面的示例应该显示给出的内容和结果。
  • 正如我在回答中指出的那样,我认为您可以通过删除 entity_id 并仅考虑单个组件来进一步简化您的示例。

标签: mysql


【解决方案1】:

您不需要表component_final 中的列enddate,因为它的值取决于同一表中的其他值:

SELECT
   id,
   source_id,
   entity_id,
   startdate,
   (  SELECT DATE_ADD(MIN(cf2.startdate),INTERVAL -1 DAY) 
      FROM component_final cf2 
      WHERE cf2.startdate > cf1.startdate
        AND cf2.source_id = cf1.source_id
        AND cf2.entity_id = cf1.entity_id
   ) as enddate,
   component_type,
   value
FROM component_final cf1;

【讨论】:

  • mhm,我如何处理 component_1 停止的第三个 source_id?据我了解,您的想法是仅插入更改的值并为已删除的组件插入 0 值?我试过你的说法,但 enddate 列总是 NULL
  • 如果您提供dbfiddle,我可以查看您的数据(示例)。
【解决方案2】:

我知道核心问题是如何找到组件更改的source_ids(0 表示删除,因此我们不希望结果中出现这些条目)以及如何在同时。为了便于说明,我稍微简化了您的示例:

  • 只有一个component_type(我考虑到可能会有连续的条目值不变)
  • entity_id只有一个,所以我们可以忽略它

将这个更简单的版本扩展到您的实际问题应该很容易。

这是一个示例输入:

source_id effective_date value
1 2020-01-01 100
2 2020-01-03 100
3 2020-01-05 80
4 2020-01-10 0
5 2020-01-12 30

我希望生成以下输出:

source_id start_date end_date value
1 2020-01-01 2020-01-04 100
3 2020-01-05 2020-01-09 80
5 2020-01-12 NULL 30

您可以通过一个查询来实现此目的,方法是将每一行与前一行连接以检查值是否已更改(查找周期的开始日期)以及未来的第一行具有不同的值(查找下一个周期的开始)。如果没有前一行,则也将其视为开始。如果以后没有更新值,我们就没有end_date

SELECT
  main.source_id,
  main.effective_date as start_date,
  DATE_SUB(next_start.effective_date, INTERVAL 1 DAY) as end_date,
  main.value
FROM source main
  LEFT JOIN source prev ON prev.effective_date = (
    SELECT MAX(effective_date)
    FROM source
    WHERE effective_date < main.effective_date
  )
  LEFT JOIN source next_start ON next_start.effective_date = (
    SELECT MIN(effective_date)
    FROM source
    WHERE effective_date > main.effective_date AND value <> main.value
  )
WHERE
  ISNULL(prev.source_id) OR prev.value <> main.value
  AND main.value <> 0
ORDER BY main.source_id

正如我所说:这必须适应您的问题,例如通过为entity_id 添加适当的连接条件。

@Luuk 指出您不需要结束日期,因为它可以从数据中得出。如果您也有“0 个句点”开头的条目,即没有设置值,就会出现这种情况。如果您没有这些条目,则无法分别从下一个时期的开始推导出结束,因为两者之间可能存在间隔。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-02
    • 2014-04-21
    • 2021-08-11
    相关资源
    最近更新 更多