检查多个接受期间的日期期间(例如许可期间与使用期间)

Posted

技术标签:

【中文标题】检查多个接受期间的日期期间(例如许可期间与使用期间)【英文标题】:Checking Date Periods across a range of multiple accepted periods (eg Licensed Periods vs Use Periods) 【发布时间】:2020-12-03 02:08:00 【问题描述】:

创建一个检查/DQ 查询,检查在该使用期间某物的使用是否获得许可。

许可记录可能重叠,但通常按顺序排列,但许可之间也可能存在时间间隔。这可能听起来有点疯狂,但这是因为许可证可能具有不同时间的类别,但我暂时忽略了这一点。

Licensed:       |------A-----------------||-C-|                     |-----E------|    |---F--|  |--G--|
                             |----------B----------||-D-|                                  |---H---|
Use:        xxxx1-||--2--| |-3-| |-4-| |--5--|    |-6-||-7xxx x8x xx9-|        |--x10x--|   |-11--|

测试是使用期限完全存在于许可范围内。

U1 - 失败,因为它只是 LA 的一部分 U2 - 使用 LA 传球 U3 - 使用 LA 传球 U4 - 使用 LB 或 LA 传球 U5 - 使用 LB OR LA & LC 传球 U6 - 使用 LB 和 LD 传球 U7 - LD 的部分重叠失败 U8 - 没有重叠失败 U9 - LE 的部分重叠失败 U10 - 失败与 LE 和 LF 重叠,但不完全重叠 U11 - 使用左手传球

在我的情况下,返回它被接受的许可证并不重要,只要它在用例期间获得许可,尽管它对调试很有用。

我不确定从哪个角度来解决这个问题 - 虽然在比较日期期间有很多问题,但同时进行这一切会节省时间,并且在简化的答案中会很有用。

一个假设是先将 L 个周期简化为汇总的 L 个范围(使用 CTE),然后对用例进行不在范围内?即:

Licensed:       |------A---------------------------|             |-----B------|   |-------C------|
Use:        xxx-1-||--2--| |-3-| |-4-| |--5--|   |-6xxx xx7x  xx8-|        |--x9x--|   |-10--|

尽管在预过滤涵盖用例的许可证时会更加简化。即:

U2 将仅查看 A,因为 L 日期范围仅涵盖 LA。

Licensed:       |------A-----------------|
Use:                 |--2--|

希望这是一个有趣的示例,它将在应用程序中作为实时检查出现 - 所以虽然我可以使用代码来执行此操作,但我希望它会更快、更明智/更易读SQL,我可能会把它变成 DQ 报告。

你会怎么做?例子。

使用 SQL Server 2016 和/或 VB.Net。

示例数据:

DROP TABLE IF EXISTS #License
DROP TABLE IF EXISTS #Use

--License table
CREATE TABLE #License 
(
     ID char(1) NOT NULL, 
     FromDate date NOT NULL, 
     ToDate date NOT NULL
)

INSERT INTO #License SELECT 'A', '2020-01-01','2020-03-01'
INSERT INTO #License SELECT 'B', '2020-02-01','2020-03-10'
INSERT INTO #License SELECT 'C', '2020-03-02','2020-03-05'
INSERT INTO #License SELECT 'D', '2020-03-11','2020-03-20'
INSERT INTO #License SELECT 'E', '2020-06-01','2020-07-01'
INSERT INTO #License SELECT 'F', '2020-08-01','2020-08-15'
INSERT INTO #License SELECT 'G', '2020-09-01','2020-09-15'
INSERT INTO #License SELECT 'H', '2020-08-10','2020-09-10'
--SELECT *, DATEDIFF(dd,FromDate,ToDate) AS Days FROM #License

--Use Cases
CREATE TABLE #Use 
(
     ID int NOT NULL, 
     FromDate date NOT NULL, 
     ToDate date NOT NULL, 
     ExpectedResult bit NOT NULL
)

INSERT INTO #Use SELECT 1, '2019-12-01','2020-01-15', 0
INSERT INTO #Use SELECT 2, '2020-01-16','2020-01-20', 1
INSERT INTO #Use SELECT 3, '2020-01-20','2020-02-05', 1
INSERT INTO #Use SELECT 4, '2020-02-10','2020-02-20', 1
INSERT INTO #Use SELECT 5, '2020-03-01','2020-03-04', 1
INSERT INTO #Use SELECT 6, '2020-03-08','2020-03-15', 1
INSERT INTO #Use SELECT 7, '2020-03-18','2020-03-25', 0
INSERT INTO #Use SELECT 8, '2020-04-01','2020-04-30', 0
INSERT INTO #Use SELECT 9, '2020-05-20','2020-06-10', 0
INSERT INTO #Use SELECT 10, '2020-06-10','2020-08-10', 0
INSERT INTO #Use SELECT 11, '2020-08-12','2020-09-08', 1
--SELECT *, DATEDIFF(dd,FromDate,ToDate) AS Days FROM #Use

-- Fails
--SELECT U.ID, ExpectedResult
--    , CASE WHEN L.ID IS NOT NULL THEN 1 ELSE 0 END AS Result
--    , CASE WHEN ExpectedResult = (CASE WHEN L.ID IS NOT NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END AS Success
--    ,*
--FROM #Use U
--LEFT JOIN #License L ON 
--    U.FromDate BETWEEN L.FromDate AND L.ToDate 
--    AND U.ToDate BETWEEN L.FromDate AND L.ToDate
----WHERE U.ID = 1
;
-- Using Gordons answer.
WITH lt AS (
    SELECT dateadd(day, 1, l.ToDate) AS test_date --, ID
    FROM #License l
)
SELECT Results.*, U.ExpectedResult, CASE WHEN U.ExpectedResult = Results.Result THEN 1 ELSE 0 END AS ExpectedIsResult
FROM (
    SELECT u.ID --, *
    , COUNT(*) AS DateTests -- the amount of dates tested against licenses (USE and LICENSE To+1 within USE)
    , COUNT(l.ID) AS TestsMatchedLicense -- the amount that matched a LICENSE
    , (CASE WHEN COUNT(*) = COUNT(l.ID) THEN 1 ELSE 0 END) AS Result -- any date that didn't match that was required Fails the result.

    FROM #Use u CROSS APPLY
         (SELECT v.test_date
          FROM (values (u.FromDate), (u.ToDate) ) AS v(test_date)
          UNION ALL
          SELECT lt.test_date --Check the the U goes past a license
          FROM lt
          WHERE lt.test_date >= u.FromDate AND
                lt.test_date <= u.ToDate
         ) AS slt 
         --Match USE range dates to a LICENSE   AND   LICENSE TO+1 within USE range matches a LICENSE - as the LICENSE range may not cover the whole USE period but another license may.
         LEFT JOIN #License l ON slt.test_date BETWEEN l.FromDate AND l.ToDate 
    GROUP BY u.ID
) AS Results INNER JOIN #Use U ON Results.ID = U.ID

使用上面的简单尝试获得了很多成功,但 U6 除外 - 但并非所有案例都匹配所有匹配的许可证,即:U5

【问题讨论】:

请以文本表格的形式提供示例数据和所需结果。 抱歉,我将构建一个 sql 示例数据并尝试 - 尝试可能需要一段时间。 【参考方案1】:

基本上,您需要测试开始日期、结束日期以及其间的所有许可日期。特别是,您关心的时间比结束日期多 1 - 潜在的非许可期的开始。

with lt as (
      select l.*, dateadd(day, 1, l.todate) as test_date
      from license l
     )
select u.id,
       (case when count(*) = count(l.id) then 'Pass'
             else 'Fail'
        end)
from uset u cross apply
     (select v.test_date
      from (values (u.fromdate), (u.todate)
           ) v(test_date)
      union all
      select lt.test_date
      from lt
      where lt.fromdate >= u.fromdate and
            lt.todate <= u.todate
     ) lt left join
     license l
     on lt.test_date between l.fromdate and l.todate
group by u.id;

Here 是一个 dbfiddle。

【讨论】:

感谢 Gordon 的回答,现在已经针对示例进行了尝试,并通过一些遗漏的更改更新了您的答案(我已经更新了问题中的脚本),我有点挣扎关于它是如何工作的——因为它按预期工作。您能否进一步了解查询的所有部分是如何工作的。否则,如果您是凭空做到的,那将是金星。 好的,我想我明白了——您匹配的所有 USE 范围日期都与 LICENSE 范围匹配,或者如果 LICENSE 不涵盖整个 USE 范围,请在 LICENSE 末尾查找一个。然后匹配所有日期以测试实际匹配许可证的......当然我自己不会想出来。谢谢。 @CooPzZ 。 .. 你用示例数据编辑了问题,所以我用 SQL Fiddle 和你的列名编辑了答案。 该示例未通过 U10。我将联合的第二个选择更改为使用 test_dates,它按预期工作。我也避免使用“lt”作为 CTE 的名称和连接日期的名称——这让我在编译查询时感到困惑,在我的脑海中,有一段时间因为它们不是相同的数据。

以上是关于检查多个接受期间的日期期间(例如许可期间与使用期间)的主要内容,如果未能解决你的问题,请参考以下文章

需要在日期期间计算行数,然后根据该日期期间使用其他条件计算行数

检查两个日期期间是不是重叠[重复]

R在POSIXlt日期序列中标记多个日期期间

合同终止日期是怎么算

Power Bi - 基于 SelectedValue 期间的前一年期间的总计

在使用 Laravel 进行验证期间检查输入中是不是存在字段