知识库

如何在 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 函数,这将大大简化这些检查。

© . All rights reserved.