SQL或SSIS查找缺少的日期范围?

时间:2016-01-22 20:48:01

标签: sql sql-server ssis common-table-expression

我有一个协议表和一个计划表。计划是协议的子女,并有FK参考。

同一协议的所有计划都应该填补协议的整个范围。但是,协议中有一些缺失的计划。

在SQL或SSIS中是否还有一个"缺少"的列表?计划?

协议表

+--------------+----------------------+--------------------+
| Agreement Id | Agreement Start Date | Agreement End Date |
+--------------+----------------------+--------------------+
|            1 | 1/1/2010             | 12/31/2016         |
+--------------+----------------------+--------------------+

计划表

+--------------+---------+-----------------+---------------+
| Agreement Id | Plan Id | Plan Start Date | Plan End Date |
+--------------+---------+-----------------+---------------+
|            1 |       1 | 1/1/2010        | 12/31/2010    |
|            1 |       2 | 1/1/2012        | 12/31/2012    |
|            1 |       3 | 1/1/2014        | 12/31/2016    |
+--------------+---------+-----------------+---------------+

所需的计划表

+--------------+---------+-----------------+---------------+
| Agreement Id | Plan Id | Plan Start Date | Plan End Date |
+--------------+---------+-----------------+---------------+
|            1 |       1 | 1/1/2010        | 12/31/2010    |
|            1 |       4 | 1/1/2011        | 12/31/2011    |
|            1 |       2 | 1/1/2012        | 12/31/2012    |
|            1 |       5 | 1/1/2013        | 12/31/2013    |
|            1 |       3 | 1/1/2014        | 12/31/2016    |
+--------------+---------+-----------------+---------------+

基本上,我想得到协议1缺失的计划,即这些行:

+--------------+---------+-----------------+---------------+
| Agreement Id | Plan Id | Plan Start Date | Plan End Date |
+--------------+---------+-----------------+---------------+
|            1 |       4 | 1/1/2011        | 12/31/2011    |
|            1 |       5 | 1/1/2013        | 12/31/2013    |
+--------------+---------+-----------------+---------------+

1 个答案:

答案 0 :(得分:1)

这是为MS SQL Server编写的,因此如果您正在为MySQL编码,则可能需要调整日期函数,但我相信这应该可行。我不认为我在协议开始时已经涵盖了缺失计划的情况,因此我会尽快给出一些想法并添加代码:

SELECT
    DATEADD(DAY, 1, P.end_date) AS start_date,
    COALESCE(DATEADD(DAY, -1, P3.start_date), A.end_date) AS end_date
FROM
    dbo.Agreements A
INNER JOIN dbo.Plans P ON
    P.agreement_id = A.agreement_id
LEFT OUTER JOIN dbo.Plans P2 ON P2.agreement_id = A.agreement_id AND P2.start_date = DATEADD(DAY, 1, P.end_date)
LEFT OUTER JOIN dbo.Plans P3 ON P3.agreement_id = A.agreement_id AND P3.start_date > P.end_date
LEFT OUTER JOIN dbo.Plans P4 ON P4.agreement_id = A.agreement_id AND P4.start_date BETWEEN P.end_date AND P3.start_date AND P4.plan_id <> P3.plan_id
WHERE
    P.end_date <> A.end_date AND
    P2.agreement_id IS NULL AND
    P4.agreement_id IS NULL

此方法还应捕获缺少的开始和结束计划,但使用窗口函数ROW_NUMBER来排序。你可以在没有ROW_NUMBER的情况下做到这一点,但它要复杂得多。我也不确定在SQL中没有更简单的方法,但这是我开始输入时首先想到的:

;WITH CTE_MissingEndDates AS
(
    SELECT agreement_id, missing_end_date, ROW_NUMBER() OVER (PARTITION BY agreement_id ORDER BY missing_end_date) AS row_num
    FROM
    (
        SELECT
            A.agreement_id,
            DATEADD(DAY, -1, P1.start_date) AS missing_end_date
        FROM
            dbo.Agreements A
        INNER JOIN dbo.Plans P1 ON P1.agreement_id = A.agreement_id
        LEFT OUTER JOIN dbo.Plans P2 ON P2.agreement_id = A.agreement_id AND P2.end_date = DATEADD(DAY, -1, P1.start_date)
        WHERE
            P1.start_date > A.start_date
        UNION
        SELECT A2.agreement_id, A2.end_date FROM dbo.Agreements A2 WHERE NOT EXISTS (SELECT * FROM dbo.Plans WHERE agreement_id = A2.agreement_id AND end_date = A2.end_date)
    ) SQ
),
CTE_MissingStartDates AS
(
    SELECT agreement_id, missing_start_date, ROW_NUMBER() OVER (PARTITION BY agreement_id ORDER BY missing_start_date) AS row_num
    FROM
    (
        SELECT
            A.agreement_id,
            DATEADD(DAY, 1, P1.end_date) AS missing_start_date
        FROM
            dbo.Agreements A
        INNER JOIN dbo.Plans P1 ON P1.agreement_id = A.agreement_id
        LEFT OUTER JOIN dbo.Plans P2 ON P2.agreement_id = A.agreement_id AND P2.start_date = DATEADD(DAY, 1, P1.end_date)
        WHERE
            P1.end_date < A.end_date
        UNION
        SELECT A2.agreement_id, A2.start_date FROM dbo.Agreements A2 WHERE NOT EXISTS (SELECT * FROM dbo.Plans WHERE agreement_id = A2.agreement_id AND start_date = A2.start_date)
    ) SQ
)
SELECT
    MSD.missing_start_date,
    MED.missing_end_date
FROM
    CTE_MissingStartDates MSD
INNER JOIN CTE_MissingEndDates MED ON
    MED.agreement_id = MSD.agreement_id AND
    MED.row_num = MSD.row_num