基于每日传入数据的SQL构造和续订日期间隔

时间:2017-07-07 20:33:56

标签: sql sql-server temporal

我每天都有以下格式的传入数据: (id, dat, state, val)其中id是唯一实体,dat是日期(即ISO'yyyy-MM-dd'),state是实体的状态,{{1} }是此状态下的实体的值。

我需要将其转换为这种格式的数据:val其中(id, state, val, dat1, dat2, month)dat1是实体dat2具有状态id的日期戳月内state值。

我很难找到一种正确的方法来收集和保存这种格式的数据。我知道解决方案必须满足以下要求:

1)如果val在新的每日数据(即2017-01-27)中将idstate更改为st1,则应该有一行这样的封闭期间:

st2

和状态(id=1, state=st1, val=2.5, dat1='2017-01-15', dat2='2017-01-26', month='2017-01-01') 的行如下:

st2

2)如果在2017-02-14上有一个表的新(id=1, state=st2, val=5.5, dat1='2017-01-27', dat2='2017-01-31', month='2017-01-01') ,那么应该有一个插入的行:

id, 因此,如果每日数据中有一个新的ID,并且此ID在之前的期间不存在,我们将其出生日期作为期间的开头,并将截止日期保持为月末,直到该ID的状态发生变化为止二月份。

3)表格的设计方式应允许在插入\更新每日新数据(双时态表?)之前具有表格版本的历史记录,以便有机会丢弃错误的数据和回滚。 / p>

4)应该有一个查询表,它能够检测到相同ID的重叠日期间隔(强制ID随时间的完整性)。

5)这类数据应该有一些必要的索引。 Id应该是外键。

6)当一个新数据到达加载表并进行1),2),3)中描述的操作时,是否应该有一个触发器工作?

有人能说出这类任务的文献或已知方法吗? 我相信这是解决的任务,但我无法确定数据模型的正确名称,并找到这种数据的任何明确示例。

1 个答案:

答案 0 :(得分:0)

下面的代码更新基于“每日数据”的实体状态表。 我已经创建了自己的样本数据,我的理解符合您的要求。

CREATE TABLE #DailyData( id INT, [state] VARCHAR( 10 ), val FLOAT, dat1 DATETIME )
CREATE TABLE #EntityState( id INT, [state] VARCHAR( 10 ), val FLOAT, dat1 DATETIME, dat2 DATETIME, [month] DATETIME )
INSERT INTO #EntityState
SELECT 1, 'st2', 4.6, '01-Jul-2017', '08-Jul-2017', '01-Jul-2017' UNION ALL
SELECT 1, 'st1', 1.2, '09-Jul-2017', '31-Jul-2017', '01-Jul-2017' UNION ALL
SELECT 2, 'st1', 2.2, '08-Jul-2017', '31-Jul-2017', '01-Jul-2017'

INSERT INTO #DailyData
SELECT 1, 'st3', 5.6, '15-Jul-2017' UNION ALL
SELECT 1, 'st2', 4.6, '01-Aug-2017' UNION ALL
SELECT 2, 'st2', 2.5, '10-Jul-2017'


SELECT 'Before', * FROM #EntityState
;WITH Q0 AS(
    -- Insert current state
    SELECT id, [state], val, dat1
    FROM
    ( SELECT id, [state], val, dat1, ROW_NUMBER() OVER( PARTITION BY id ORDER BY dat1 DESC ) AS LatestDate
    FROM #EntityState ) AS CurrentState
    WHERE LatestDate = 1
    UNION ALL
    -- Combine with Daily Import
    SELECT id, [state], val, dat1
    FROM #DailyData
)
,
-- Add Date order column
Q1 AS (
    SELECT id, [state], val, dat1, ROW_NUMBER() OVER( PARTITION BY id ORDER BY dat1 ASC ) AS DateOrder
    FROM Q0
    )
-- Merge new Entity states with Existing
MERGE INTO #EntityState AS T
    USING
            -- New Entity states 
            ( SELECT Prev.id, Prev.[state], Prev.val, Prev.dat1, Next.dat1 AS dat2
            FROM Q1 AS Prev
                LEFT JOIN Q1 AS Next ON Prev.id = Next.id AND Prev.DateOrder + 1 = Next.DateOrder ) AS S
        ON T.id = S.id AND T.dat1 = S.dat1 AND T.[state] = S.[state]
    -- Update dat2 on last day's state
    WHEN MATCHED AND T.dat2 = EOMONTH( T.dat1 )
        THEN UPDATE 
            SET T.dat2 = S.dat2
    -- Add new Entity State
    WHEN NOT MATCHED BY TARGET THEN
        INSERT( id, state, val, dat1, dat2, [month] )
            VALUES( S.id, S.state, S.val, S.dat1, ISNULL( S.dat2, EOMONTH( S.dat1 )),
                    DATEADD( month, DATEDIFF( month, 0, S.dat1 ), 0 ));

SELECT 'After', * FROM #EntityState

此代码改编自: Count seconds on switch interval SQL Server