我们如何对Neo4j图进行连续遍历?

时间:2017-06-14 19:16:50

标签: php neo4j cypher

我正在构建一个PHP Web应用程序并使用Neo4j进行图形表示。

该应用程序允许构建代表产品部分的页面。然后将这些页面串在一起以形成产品页面组件的层次结构和顺序。理由是,在构建更多产品时,用户可以重用已经为其他产品构建的组件(包括子部件)。这意味着,产品之间存在多对多的关系。

为了表示这种结构,图表是合适的。但是,问题仍然是如何允许最终用户按顺序浏览产品。我需要的是用户正在查看的每个“当前节点”,以了解下一个和前一个节点。此问题不仅限于页面的兄弟,也适用于 0..n-degree兄弟姐妹(即兄弟姐妹,叔叔节点,伟大的叔叔节点等)。

我可以使用一系列简单的 NODE-relationship-NODE 三元组来轻松地表示产品结构:

({id: 1000})-[:OWNS]->({id: 12})   // assume 1000 is a product node
({id: 1000})-[:OWNS]->({id: 13})   // with 12, 13 as its categories

// 12's descendant pages
({id: 12})-[:OWNS]->({id: 22})
({id: 12})-[:OWNS]->({id: 23})
({id: 12})-[:OWNS]->({id: 24})

// 13's descendant pages
({id: 13})-[:OWNS]->({id: 25})
({id: 13})-[:OWNS]->({id: 26})
({id: 13})-[:OWNS]->({id: 27})

[:OWNS]关系包含'order'属性和'added'时间戳,它们一起允许父级及其子级之间的关系按照顺序导航中子节点的所需消费顺序进行排序。

我的目标是允许在其中一个视图中按顺序导航产品的节点并进行导航。

以下是这将如何运作:

最终用户通过选择产品开始导航。打开产品应显示其第一级子节点。选择节点后,将显示页面内容(这将成为当前节点)。然后,用户可以选择继续下一页,而无需通过单击下一步按钮返回到父菜单。每个页面都会显示NEXT和PREV按钮。

要导航产品的层次结构(可能有多层嵌套),我们会显示当前节点的第一个子节点,检查更多子节点,并以深度优先遍历它们。当下面没有更多级别时,预期的行为是检查当前节点的兄弟姐妹是否正确,当所有兄弟姐妹都被消耗时,我想遍历到父节点,遍历其右兄弟节点,如果有的话,等等。我相信这是一次深度优先的遍历。

相同的遍历将适用于“PREV”按钮。

从我的所有努力中,我发现在每个页面加载时计算下一个/上一个节点太复杂了。我决定为所有节点预先计算[:NEXT]关系,并在产品创建或编辑期间按顺序进行更改时批量更新。我认为如果导航索引在重新计算之前稍微过时,它不会有害。

基于上面的例子,所需的输出是这样的:

// assuming OWNS.order = 0 for all 
// and OWNS.added = timestamp increases with each CREATE operation:
({id: 1000})-[:NEXT]->({id: 12})-[:NEXT]->({id: 22})-[:NEXT]->({id: 23})-[:NEXT]->({id: 24})-[:NEXT]->({id: 13})-[:NEXT]->({id: 25})-[:NEXT]->({id: 26})-[:NEXT]->({id: 27}) 

换句话说,顺序中的节点应为:

1000, 12, 22, 23, 24, 13, 25, 26, 27

在同一父级下计算同一级别的下一个项目很容易。但我发现困难的是一个创建以下关系的查询:

  1. 父母(P_ {i})与其第一个(最左侧)子女(C_ {0})之间的关系

  2. 父母(P_ {i})的最后一个(最右边)孩子(C_ {n})与父母的下一个右兄弟(P_ {i + 1})之间的关系。

  3. 在没有孩子或兄弟姐妹的情况下,可以找到关系中的1级或更多级别的链接。

    我愿意接受任何建议,即使没有预先计算。如何以不同的方式思考这一点的任何想法将不胜感激。

    由于

    *编辑*

    我到目前为止:

    MATCH (cur)
    
    // Use optional matches because not all nodes will have these
    
    // Match if has children
    OPTIONAL MATCH (cur)-[rc:OWNS]->(child)
    
    // Looking for next siblings, uncles, or great uncles. To do this
    // i isolate the part of the pattern that might attach in between 
    // ancestors all the way until the ancestor of interest (named parent) 
    // which a suitable next sibling/uncle/great uncle/etc
    // i want a reference to r and r2 so they can be compared.
    // And note that rz has a 0 or more requirement, which 
    // means potentially (cur) and (betweenparents) end up being equal
    // and that (sibling) could end up being on the same level.
    
    OPTIONAL MATCH (cur)<-[rz:OWNS*0..]-(betweenparents)<-[r:OWNS]-(parent)-[r2:OWNS]->(sibling)
    
    // Condition is that the (sibling) node is considered "next to"
    // the (current) node.
    WHERE r2.order >= r.order AND r2.added > r.added
    
    // Now order by first the current node's children if any as
    // they have priority, then by the distance of the parent who has
    // the suitable sibling/uncle/great uncle/etc, followed by, followed
    // by the intended order of the sibling nodes (this puts at the head
    // of the list the very immediately next item of both child
    // and sibling) 
    WITH cur, child, sibling, r2, size(rz) as depth, rc
    ORDER BY rc.added, depth, r2.added
    
    // Now can grab the head child element and head sibling element
    // if either exists, which are (hopefully) guaranteed to be 
    // ordered sequentially "next" of the current node.
    WITH cur, HEAD(COLLECT(child)) as c, HEAD(COLLECT(sibling)) as s 
    
    return cur.id, c.id, s.id
    ORDER BY cur.id
    

    我在这里走在正确的轨道上吗?或者我只是简单地复杂化了比这更简单的东西?

    此外,我正在获得命名sz多重关系的弃用通知,我需要标记这样我可以通过增加距离来订购。有没有更好的方法来解决这个问题?

1 个答案:

答案 0 :(得分:0)

好吧,看起来我有一个解决方案。对任何知道如何优化它或者可以在其中挖洞的人都会非常开放。

我所知道的是,我可以通过我的理论示例陷入困境,您将在下面看到这一点。验证何时重用具有[:OWNS]关系的节点至关重要,我们没有第二个[:OWNS]关系导致它在地图中更高。实际上,这为到达节点提供了两条可能的途径并造成了各种各样的破坏。

首先,让我们从测试图开始。下面不是完全有效的语法,但合理地演示了这些关系:

MERGE (post:module{id: 21})-[:OWNS {order:0, added:1}]->(post:module{id: 25})
MERGE (post:module{id: 18})-[:OWNS {order:0, added:2}]->(post:module{id: 26})

MERGE (post:product{id: 1000})-[:OWNS {order:0, added:3}]->(post:module{id: 10})
MERGE (post:product{id: 1000})-[:OWNS {order:0, added:4}]->(post:module{id: 11})
MERGE (post:product{id: 1000})-[:OWNS {order:0, added:5}]->(post:module{id: 12})

MERGE (post:module{id: 10})-[:OWNS {order:0, added:6}]->(post:module{id: 13})
MERGE (post:module{id: 10})-[:OWNS {order:0, added:7}]->(post:module{id: 14})

MERGE (post:module{id: 11})-[:OWNS {order:0, added:8}]->(post:module{id: 15})

MERGE (post:module{id: 12})-[:OWNS {order:0, added:9}]->(post:module{id: 16})
MERGE (post:module{id: 12})-[:OWNS {order:0, added:10}]->(post:module{id: 17})

MERGE (post:module{id: 16})-[:OWNS {order:0, added:11}]->(post:module{id: 18})
MERGE (post:module{id: 16})-[:OWNS {order:0, added:12}]->(post:module{id: 19})

MERGE (post:product{id: 1001})-[:OWNS {order:0, added:13}]->(post:module{id: 20})
MERGE (post:product{id: 1001})-[:OWNS {order:0, added:15}]->(post:module{id: 23})

MERGE (post:module{id: 20})-[:OWNS {order:0, added:16}]->(post:module{id: 21})
MERGE (post:module{id: 20})-[:OWNS {order:0, added:17}]->(post:module{id: 24})

// These will cause a loop, and have been remarked out as invalid. 
// One would need to assert no such relations are created by checking
// for an existing shared ancestor of the two nodes being attempted 
// to be connected.

//MERGE (post:module{id: 21})-[:OWNS {order:0, added:18}]->(post:module{id: 12})
//MERGE (post:module{id: 18})-[:OWNS {order:0, added:19}]->(post:module{id: 10})

MERGE (post:product{id: 1001})-[:OWNS {order:0, added:20}]->(post:module{id: 16})
MERGE (post:product{id: 1002})-[:OWNS {order:0, added:21}]->(post:module{id: 22})

MERGE (post:module{id: 22})-[:OWNS {order:0, added:22}]->(post:module{id: 12})
MERGE (post:module{id: 22})-[:OWNS {order:0, added:23}]->(post:module{id: 21})

现在我们知道了测试图的样子,这段代码为所有节点添加了一个[:NEXT]关系到顺序的下一个节点,基本上是图表的递归“自我优先”步骤:

MATCH (cur)

// Use optional matches because not all nodes will have a valid next

// Match if has children
OPTIONAL MATCH (cur)-[rc:OWNS]->(child)

// Looking for next siblings, uncles, or great uncles. To do this
// i isolate the part of the pattern that might attach in between 
// ancestors all the way until the ancestor of interest (named parent) 
// which is a suitable next sibling/uncle/great uncle/etc
// i want a reference to r and r2 so they can be compared.
// And note that rz has a 0 or more requirement, which 
// means potentially (cur) and (betweenparents) end up being equal
// and that (sibling) could end up being on the same level as
// (current).

OPTIONAL MATCH (cur)<-[rz:OWNS*0..]-(betweenparents)<-[r:OWNS]-(parent)-[r2:OWNS]->(sibling)

// Condition is that the (sibling) node is considered "next to"
// the (current) node.
WHERE r2.order >= r.order AND r2.added > r.added

// Now order by first the current node's children if any as
// they have priority, then by the distance of the parent who has
// the suitable sibling/uncle/great uncle/etc, followed
// by the intended order of the sibling nodes (this puts at the head
// of the results the immediately next items of both child
// and sibling).

WITH cur, child, sibling, r2, size(rz) as depth, rc
ORDER BY rc.order, rc.added, depth, r2.order, r2.added

// Now can grab the head child element or head sibling element
// if one is not null. These are guaranteed to be 
// ordered sequentially starting from the "next" of the 
// current node.

WITH cur, HEAD(FILTER( i in COLLECT(coalesce(child,sibling)) WHERE i is NOT NULL ))  as s 

// Create relationship from cur to the head item child or sibling 
// that was found to be a valid next.
foreach(i in CASE WHEN s IS NULL then [] else [s] end |
    MERGE (cur)-[:NEXT]->(i)
)

see graph result

添加关系后,查询图表对于任何当前节点的下一个和上一个项目都很容易。例如:

// TO QUERY THE previous, cur, next nodes
OPTIONAL MATCH (cur:post{id: 23})-[:NEXT]->(next)<-[:OWNS*0..]-(context:product{id: 1001})
OPTIONAL MATCH (cur)<-[:NEXT]-(prev)<-[:OWNS*0..]-(context) 
return prev, cur,next

请注意使用(上下文)节点过滤掉不属于当前正在探索的产品的路径,但仍允许产品从其他产品导入节点。

如果您有任何改进建议,我很乐意听到。我确信这是低效的,但这是我第一次使用Neo4j,这是我能想到的最好的。它有效。无论如何它都将被批量预先计算,并且只在运行时查询下一个和上一个节点。