如何在 Cypher 中检查时间范围重叠
Neo4j 3.4 版本在 Cypher 中引入了时态类型,因此现在我们有了日期、日期时间及其本地版本,以及持续时间。
虽然我们没有专门的时间范围类型,但我们可以使用两个时态瞬间作为范围的开始和结束。
虽然检查一个时态瞬间是否落在某个时间范围内很简单,但计算两个时间范围是否重叠则复杂得多。
本文提供了一个简单的逻辑公式来检查两个时间范围是否重叠,并提供了如何在 Cypher 中应用该公式的示例。
我们将使用一个简单的图来作为示例。
CREATE (:Event {id:1, start:date(), end:date() + duration({days:5})}),
(:Event {id:2, start:date() + duration({days:3}), end:date() + duration({days:8})})
事件 2 的开始和结束日期都比事件 1 晚 3 天。这些事件重叠。
检查时间范围重叠的简化公式
时间范围有 4 种可能的重叠方式,不包括它们是相同范围或切向相邻(一个在另一个结束时开始)的情况(我们可以对这些情况使用包含性不等式)。
虽然我们不会涵盖细节,但重点是计算重叠通常并非易事,很容易创建出遗漏某些类型重叠的计算,从而导致不正确的结果。
值得庆幸的是,有一个逻辑简化(此处已证明),最终相对简单
Max(StartA, StartB) <= Min(EndA, EndB)
若要时间范围重叠,最晚的开始时间必须发生在最早的结束时间之前(或同时)。
Cypher 没有我们可以使用的标量 min() 和 max() 函数(它们都是聚合函数,不适用于解决此问题),因此我们需要一种替代方法。
使用 CASE 的纯 Cypher 方法
我们可以使用 CASE 功能来实现标量 min() 和 max() 操作。
MATCH (e1:Event {id:1}), (e2:Event {id:2})
WITH CASE WHEN e1.start >= e2.start THEN e1.start ELSE e2.start END as maxStart,
CASE WHEN e1.end <= e2.end THEN e1.end ELSE e2.end END as minEnd
RETURN maxStart <= minEnd as rangesOverlap
上述返回 true,因此这些范围确实重叠。
我们可以在 RETURN 中使用 CASE 评估,但如果我们将 min() 和 max() 计算与重叠公式的应用分开,会更容易查看和理解。
使用集合函数的 APOC 方法
APOC 过程包含一些用于计算集合最大值和最小值的函数,即 apoc.coll.max()
和 apoc.coll.min()
。
虽然这些看起来是完成这项工作的正确工具,但仍存在一个问题:截至 2020 年 4 月,这些函数尚不支持时态类型,但修复程序即将推出。
修复到位后,它将如下所示
MATCH (e1:Event {id:1}), (e2:Event {id:2})
RETURN apoc.coll.max([e1.start, e2.start]) <= apoc.coll.min([e1.end, e2.end]) as rangesOverlap
在此之前,还有另一种解决方法(除了上面的纯 Cypher 示例),通过比较 epochMillis 值来实现,但这要求我们使用 dateTime 类型。如果我们只有日期,可以使用 dateTime({date:date()}) as dateTimeFromADate
从中派生出 dateTime。
由于前面创建的示例节点是日期类型,我们需要进行这种转换
MATCH (e1:Event {id:1}), (e2:Event {id:2})
WITH apoc.coll.max([dateTime({date:e1.start}).epochMillis, dateTime({date:e2.start}).epochMillis]) as maxStart,
apoc.coll.min([dateTime({date:e1.end}).epochMillis, dateTime({date:e2.end}).epochMillis]) as minEnd
RETURN maxStart <= minEnd as rangesOverlap
您可能会认为,如果有一个可调用的 overlap() 函数(接受范围的开始和结束)会容易得多,我们同意这一点。
我们正在开发一个 APOC 函数,这将大大简化这些检查。
此页面有帮助吗?