非线性模式

Cypher® 可用于表达非线性模式,方法是使用等值连接(路径中多个节点关系相同的操作)或更复杂的图模式,这些模式由多个路径模式组成。

等值连接

等值连接是对路径的操作,它要求路径中的多个节点或关系相同。节点或关系之间的相等性是通过在多个节点模式或关系模式中声明相同变量来指定的。等值连接允许在路径模式中指定循环。

本节使用以下图

patterns equijoins

要重新创建图,请对空 Neo4j 数据库运行以下查询

CREATE (bhi:Station {name: 'Birmingham International'}),
  (cov:Station {name: 'Coventry'}),
  (eus:Station  {name: 'London Euston'}),
  (bhi)<-[:CALLS_AT]-(s1:Stop {departs: time('12:03')}),
  (cov)<-[:CALLS_AT]-(s2:Stop {departs: time('11:33')})-[:NEXT]->(s1),
  (eus)<-[:CALLS_AT]-(s3:Stop {departs: time('15:54')}),
  (cov)<-[:CALLS_AT]-(s4:Stop {departs: time('14:45')})-[:NEXT]->(s3),
  (cov)<-[:CALLS_AT]-(s5:Stop {departs: time('09:34')}),
  (eus)<-[:CALLS_AT]-(s6:Stop {departs: time('08:40')})-[:NEXT]->(s5)

为了说明等值连接的工作原理,我们将使用在两个Stations之间找到往返行程的问题。

在这个示例场景中,乘客从London EustonStation开始其出站行程,并在CoventryStation结束。返回行程将是这些Stations的相反顺序。

该图有三种不同的服务,其中两种将构成所需的往返行程,第三种将把乘客送到Birmingham International

解决方案是以下带有循环的路径

patterns equijoins solution2

如果在路径中循环“连接”发生的节点上存在唯一属性,那么可以使用匹配唯一属性的谓词来重复节点模式。以下基序演示了如何实现这一点,重复一个Station节点模式,其名称为London Euston

patterns equijoins motif

等效的路径模式是

(:Station {name: 'London Euston'})<-[:CALLS_AT]-(:Stop)-[:NEXT]->(:Stop)
  -[:CALLS_AT]->(:Station {name: 'Coventry'})<-[:CALLS_AT]-(:Stop)
  -[:NEXT]->(:Stop)-[:CALLS_AT]->(:Station {name: 'London Euston'})

可能存在没有唯一谓词的情况。在这种情况下,可以使用等值连接通过使用重复的节点变量来定义所需的循环。在当前示例中,如果在第一个和最后一个节点模式中声明相同的节点变量n,那么节点模式必须匹配同一个节点

patterns equijoins motif2

将带有等值连接的此路径模式放在查询中,可以返回出站和返回行程的时间

查询
MATCH (n:Station {name: 'London Euston'})<-[:CALLS_AT]-(s1:Stop)
  -[:NEXT]->(s2:Stop)-[:CALLS_AT]->(:Station {name: 'Coventry'})
  <-[:CALLS_AT]-(s3:Stop)-[:NEXT]->(s4:Stop)-[:CALLS_AT]->(n)
RETURN s1.departs+'-'+s2.departs AS outbound,
  s3.departs+'-'+s4.departs AS `return`
表 1. 结果
出站 返回

"08:40:00Z-09:34:00Z"

"14:45:00Z-15:54:00Z"

行数:1

图模式

除了单个路径模式之外,还可以将多个路径模式组合在逗号分隔列表中以形成图模式。在图模式中,每个路径模式都是单独匹配的,并且在各个路径模式中重复节点变量的地方,解决方案通过等值连接进行减少。如果各个路径模式之间没有等值连接,则结果是各个解决方案之间的笛卡尔积。

以这种方式连接多个路径模式的好处是,它允许指定比单个路径模式允许的线性路径更复杂的模式。为了说明这一点,将使用从铁路模型中提取的另一个示例。在这个例子中,一名乘客从Starbeck前往Huddersfield,在Leeds换乘。要从Starbeck到达Leeds,乘客可以乘坐直达服务,该服务在途经的所有车站都停靠。但是,在前往Leeds的途中,乘客有机会在其中一个车站(Harrogate)换乘,并搭乘快车,这可能使乘客能够搭乘从Leeds出发的更早的列车,从而减少总行程时间。

本节使用以下图,显示乘客可以使用哪些列车服务

patterns graph patterns graph1

要重新创建图,请对空 Neo4j 数据库运行以下查询

CREATE (hgt:Station {name: 'Harrogate'}), (lds:Station {name: 'Leeds'}),
(sbe:Station {name: 'Starbeck'}), (hbp:Station {name: 'Hornbeam Park'}),
(wet:Station {name: 'Weeton'}), (hrs:Station {name: 'Horsforth'}),
(hdy:Station {name: 'Headingley'}), (buy:Station {name: 'Burley Park'}),
(pnl:Station {name: 'Pannal'}), (hud:Station {name: 'Huddersfield'}),
(s9:Stop {arrives: time('11:53')}),
(s8:Stop {arrives: time('11:44'), departs: time('11:45')}),
(s7:Stop {arrives: time('11:40'), departs: time('11:43')}),
(s6:Stop {arrives: time('11:38'), departs: time('11:39')}),
(s5:Stop {arrives: time('11:29'), departs: time('11:30')}),
(s4:Stop {arrives: time('11:24'), departs: time('11:25')}),
(s3:Stop {arrives: time('11:19'), departs: time('11:20')}),
(s2:Stop {arrives: time('11:16'), departs: time('11:17')}),
(s1:Stop {departs: time('11:11')}), (s21:Stop {arrives: time('11:25')}),
(s211:Stop {departs: time('11:00')}), (s10:Stop {arrives: time('11:45')}),
(s101:Stop {departs: time('11:20')}), (s11:Stop {arrives: time('12:05')}),
(s111:Stop {departs: time('11:40')}), (s12:Stop {arrives: time('12:07')}),
(s121:Stop {departs: time('11:50')}), (s13:Stop {arrives: time('12:37')}),
(s131:Stop {departs: time('12:20')}),
(lds)<-[:CALLS_AT]-(s9), (buy)<-[:CALLS_AT]-(s8)-[:NEXT]->(s9),
(hdy)<-[:CALLS_AT]-(s7)-[:NEXT]->(s8), (hrs)<-[:CALLS_AT]-(s6)-[:NEXT]->(s7),
(wet)<-[:CALLS_AT]-(s5)-[:NEXT]->(s6), (pnl)<-[:CALLS_AT]-(s4)-[:NEXT]->(s5),
(hbp)<-[:CALLS_AT]-(s3)-[:NEXT]->(s4), (hgt)<-[:CALLS_AT]-(s2)-[:NEXT]->(s3),
(sbe)<-[:CALLS_AT]-(s1)-[:NEXT]->(s2), (lds)<-[:CALLS_AT]-(s21), (hgt)<-[:CALLS_AT]-(s211)-[:NEXT]->(s21), (lds)<-[:CALLS_AT]-(s10), (hgt)<-[:CALLS_AT]-(s101)-[:NEXT]->(s10), (lds)<-[:CALLS_AT]-(s11), (hgt)<-[:CALLS_AT]-(s111)-[:NEXT]->(s11), (hud)<-[:CALLS_AT]-(s12), (lds)<-[:CALLS_AT]-(s121)-[:NEXT]->(s12), (hud)<-[:CALLS_AT]-(s13), (lds)<-[:CALLS_AT]-(s131)-[:NEXT]->(s13)

该问题的解决方案组装了一组路径模式,这些模式匹配以下三个部分:停靠服务;快车服务;以及从LeedsHuddersfield的旅程的最后一段。每次换乘,从停靠服务到快车服务,以及从快车服务到继续服务,都必须尊重这样一个事实,即前一段的到达时间必须在下一段的出发时间之前。这将在单个WHERE子句中进行编码。

以下内容使用不同的颜色可视化三个部分,并标识用于创建等值连接和锚定的节点变量

patterns graph patterns graph2

对于停靠服务,假设乘客需要换乘的车站未知。因此,该模式需要在节点b(在换乘车站l停靠的Stop)之前和之后匹配可变数量的停靠站。这是通过在节点b的两侧放置量化关系-[:NEXT]->*来实现的。路径的末端也需要锚定在从Starbeck出发时间为11:11Stop,以及在调用LeedsStop

(:Station {name: 'Starbeck'})<-[:CALLS_AT]-
  (a:Stop {departs: time('11:11')})-[:NEXT]->*(b)-[:NEXT]->*
  (c:Stop)-[:CALLS_AT]->(lds:Station {name: 'Leeds'})

对于快车服务,路径的末端锚定在停靠站bLeeds站,lds将由第一段绑定。尽管在这个特定情况下,服务上只有两个停靠站,但使用了可以匹配任何数量停靠站的更通用模式

(b)-[:CALLS_AT]->(l:Station)<-[:CALLS_AT]-(m:Stop)-[:NEXT]->*
  (n:Stop)-[:CALLS_AT]->(lds)

请注意,由于 Cypher 仅允许在一个给定的图模式匹配中遍历一次关系,因此保证第一段和第二段是不同的列车服务。(有关更多详细信息,请参阅关系唯一性。)类似地,另一个量化关系跨越了在Leeds站和Huddersfield站停靠的停靠站

(lds)<-[:CALLS_AT]-(x:Stop)-[:NEXT]->*(y:Stop)-[:CALLS_AT]->
  (:Station {name: 'Huddersfield'})

其他节点变量用于WHERE子句或返回数据。将这些放在一起,生成的查询将返回通过切换到快车服务实现的最早到达时间

查询
MATCH (:Station {name: 'Starbeck'})<-[:CALLS_AT]-
        (a:Stop {departs: time('11:11')})-[:NEXT]->*(b)-[:NEXT]->*
        (c:Stop)-[:CALLS_AT]->(lds:Station {name: 'Leeds'}),
      (b)-[:CALLS_AT]->(l:Station)<-[:CALLS_AT]-(m:Stop)-[:NEXT]->*
        (n:Stop)-[:CALLS_AT]->(lds),
      (lds)<-[:CALLS_AT]-(x:Stop)-[:NEXT]->*(y:Stop)-[:CALLS_AT]->
        (:Station {name: 'Huddersfield'})
WHERE b.arrives < m.departs AND n.arrives < x.departs
RETURN a.departs AS departs,
       l.name AS changeAt,
       m.departs AS changeDeparts,
       y.arrives AS arrives
ORDER BY y.arrives LIMIT 1
表 2. 结果
出发 换乘 换乘出发 到达

"11:11:00Z"

"Harrogate"

"11:20:00Z"

"12:07:00Z"

行数:1