sql - 检查多个接受期间的日期期间(例如许可期间与使用期间)
问题描述
创建一个检查/DQ 查询,以检查在该使用期间某项的使用是否已获得许可。
许可记录可能重叠,但通常按顺序排列,但许可之间也可能存在时间间隔。这可能听起来有点疯狂,但这是因为许可证可能有不同时间的类别,但我暂时忽略了这一点。
Licensed: |------A-----------------||-C-| |-----E------| |---F--| |--G--|
|----------B----------||-D-| |---H---|
Use: xxxx1-||--2--| |-3-| |-4-| |--5--| |-6-||-7xxx x8x xx9-| |--x10x--| |-11--|
测试是使用期限完全存在于许可范围内。
- U1 - 失败,因为它只是洛杉矶的一部分
- U2 - 使用 LA 传球
- U3 - 使用 LA 传球
- U4 - 使用 LB 或 LA 传球
- U5 - 使用 LB OR LA & LC 传球
- U6 - 使用 LB 和 LD 传球
- U7 - LD的失效部分重叠
- U8 - 没有重叠
- U9 - LE 的失效部分重叠
- U10 - 失败与 LE 和 LF 重叠但不完全重叠
- U11 - 使用左手传球
在我的情况下,返回它被接受的许可证并不重要,只是它在用例期间获得了许可,尽管它对调试很有用。
我不确定从哪个角度来解决这个问题——虽然在比较日期期间有很多 Q,但同时这样做会节省时间,而且在简化的答案中会很有用。
一个假设是先将 L 个周期简化为汇总的 L 个范围(使用 CTE),然后对用例进行不在范围内?IE:
Licensed: |------A---------------------------| |-----B------| |-------C------|
Use: xxx-1-||--2--| |-3-| |-4-| |--5--| |-6xxx xx7x xx8-| |--x9x--| |-10--|
尽管在预过滤涵盖用例的许可证时会更加简化。IE:
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
解决方案
基本上,您需要测试开始日期、结束日期以及介于两者之间的所有许可日期。特别是,您比结束日期更关心 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;
这是一个 db<>fiddle。
推荐阅读
- python - Python - 如何删除不平衡的括号和引号
- r - 比较点模式,每个模式都有不同的窗口
- python - “源字符换行符”是什么意思?
- c++ - 纹理图像未正确映射
- mysql - Laravel Eloquent 模型按局部列和关系模型列值排序
- java - hibernate为不带括号的数组生成代码
- mysql - Mysql 基于行的二进制日志记录在某些版本的 mysql 中表现不同(行分组)
- scala - Flink-1.4.2 出现错误:对象 contrib 不是包 org.apache.flink 的成员
- c - 嵌入式应用程序中的最佳任务调度策略
- python - 变量赋值中“and”的用例