历史表上的合并操作

Merge operations on historic table

提问人:George Menoutis 提问时间:10/31/2023 最后编辑:George Menoutis 更新时间:10/31/2023 访问量:79

问:

我想知道是否存在一种成熟的算法来对时态表执行合并操作。

举个最小的例子,假设我们有一个表 [pricedata],如下所示:

+------+------+--------+
| from | to   | price  |
+------+------+--------+
| 2008 | 2009 | 100    |
| 2009 | 2011 | 121    |
| 2011 | 2013 | 142    |
+------+------+--------+

(此处的 [to] 列应该不包含自身)。 现在,我们得到一些新数据来覆盖原始数据:

+------+------+--------+
| from | to   | price  |
+------+------+--------+
| 2010 | 2012 | 109    |
+------+------+--------+

预期结果应相应地拆分和重新排列价格:

+------+------+--------+
| from | to   | price  |
+------+------+--------+
| 2008 | 2009 | 100    |
| 2009 | 2010 | 121    |
| 2010 | 2012 | 109    |
| 2012 | 2013 | 142    |
+------+------+--------+

我故意省略了太多细节,因为问题是是否已经有一个重要的资源/算法已经这样做了。感觉很普遍,需要在身边,尽管我的网络搜索没有产生任何东西。

sql-server 间隙和孤岛 temporal

评论

1赞 Charlieface 10/31/2023
用重新计算的数据替换整个时态表是否可行,或者它是否是一个非常大的表,其中需要保留现有的物理行?
1赞 Thom A 10/31/2023
仅供参考,时态表是一种特定类型的对象是 SQL Server,它存储自身的历史记录;这似乎不是您在这里询问的对象类型。
0赞 siggemannen 10/31/2023
我不知道是否有任何既定的算法,但在我看来,您需要: 1. 完全删除新范围内的行 2.将 ENDDATE 更改为 ENDDATE 为 ROW,其中 ENDDATE >新范围 3 的 STARTDATE。将 StartDate 更改为 StartDate 为 StartDate < EndDate 为新范围的 EndDate 的行
1赞 Andrew 10/31/2023
您是要存储所需的结果,还是存储各个行并显示所需的结果?
0赞 George Menoutis 10/31/2023
@Thom 一个很好的收获,改变了主题

答:

1赞 T N 10/31/2023 #1

注意此答案已对原始帖子进行了重大修改,以正确处理需要拆分的重叠情况。

我不确定是否有一个成熟的(众所周知的)算法,但我相信以下内容可能包括您正在寻找的逻辑。

对于具有包含开始日期和独占结束日期的数据,重叠日期的检验为 。(A.FromDate < B.ToDate AND B.FromDate < A.ToDate)

在插入新行之前,您可以查询与要插入的日期范围重叠的任何现有行,并调整现有的开始日期和结束日期。这些都需要调整、拆分甚至删除,以消除重叠。

  1. 如果新范围与现有行的下限重叠,则应调整现有行的值。from
  2. 如果新范围与现有行的上限重叠,则应调整现有行的值。to
  3. 如果新范围完全包含在现有行的范围内,则需要复制和调整该现有行,以覆盖未被新插入所取代的剩余下限和上限范围。
  4. 如果任何现有行完全包含在要插入的新范围内,则这些行可能会被删除。

我假设新数据总是会取代具有重叠范围的现有数据,并且完全被取代的行可能会被删除。

以下逻辑将处理上述所有情况:

-- Delete any row completely superseded by new insert
DELETE pricedata
WHERE [from] >= @InsertFrom
AND [to] <= @InsertTo 

-- Duplicate and adjust any row split by new insert, leaving lower residual
INSERT pricedata ([from], [to], price)
SELECT [from], @InsertFrom, price
FROM pricedata
WHERE [from] < @InsertFrom
AND [to] > @InsertTo 

-- Adjust original row split by new insert, leaving upper residual
UPDATE pricedata
SET [from] = @InsertTo
WHERE [from] < @InsertFrom
AND [to] > @InsertTo 

-- Trim lower end of row overlapping upper end of insert
UPDATE pricedata
SET [from] = @InsertTo
WHERE [from] >= @InsertFrom
AND [from] < @InsertTo 

-- Trim upper end of row overlapping lower end of insert
UPDATE pricedata
SET [to] = @InsertFrom
WHERE [to] > @InsertFrom
AND [to] <= @InsertTo 

-- Now that we have resolved any overlaps, we are clear to insert the new row
INSERT pricedata ([from], [to], price)
VALUES (@InsertFrom, @InsertTo, @InsertPrice)

请参阅此 db<>fiddle,它将上述逻辑封装在存储过程中,并演示了其在 OP 方案和其他几种情况下的操作。

评论

0赞 T N 10/31/2023
上述答案已与原始帖子进行了重大修改,以妥善处理需要拆分的重叠案例。
0赞 George Menoutis 10/31/2023
我很感激你的努力。我已经得到了一个答案,因为这样的算法并不存在。完成后,我会考虑发布我的代码。它比这复杂得多,因为已经存在的行和输入的组合应该涵盖任何多对多匹配的情况,
0赞 Charlieface 10/31/2023
允许多次插入的解决方案也很好。
0赞 T N 10/31/2023
@Charlieface - 它可能会变得复杂。我可以想象一种人为的情况,即现有行(范围)在两端被修剪,并根据传入的更新在中间分成三个方向。如果对现有行匹配(完全包含在)的传入行进行排序、编号并用于生成多个拆分行,则可能是可行的。这将是一个挑战。假设传入数据中没有重叠,其他重叠情况应该很简单。一种特殊情况是两个输入行一起通过从两侧攻击现有行来消除它。