检查多个接受期间的日期期间(例如许可期间与使用期间)
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 的名称和连接日期的名称——这让我在编译查询时感到困惑,在我的脑海中,有一段时间因为它们不是相同的数据。以上是关于检查多个接受期间的日期期间(例如许可期间与使用期间)的主要内容,如果未能解决你的问题,请参考以下文章
需要在日期期间计算行数,然后根据该日期期间使用其他条件计算行数