【问题标题】:Cypher no loops, no double pathsCypher 没有循环,没有双路径
【发布时间】:2016-12-06 04:25:31
【问题描述】:

我目前正在为一个包含 50.000 多个节点的数据库建模,每个节点都有 2 个有向关系。我尝试获取一个输入节点(根节点)的所有节点,这些节点通过一种关系连接到它,并且这些节点的所有所谓的子节点等等,直到到达每个直接和间接连接到该根节点的节点.

String query =
  "MATCH (m {title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo*..]->(n) " +
  "RETURN DISTINCT n.title AS Title, n.namespaceID " + 
  "ORDER BY n.title";

Result result = db.execute(query, params);
String infos = result.resultAsString();

我已经读到运行时更有可能在 O(n^x) 中,但我找不到任何命令排除例如循环或到一个节点的多条路径,因此查询需要 2 多个小时,而事实并非如此我的用例可以接受。

【问题讨论】:

  • Cypher 中没有 GROUP BY 运算符。你的意思是ORDER BY
  • 嘿,是的,我的错。如果类似的东西在这里有效,那只是一次尝试。我忘记删除它。它也没有与 ORDER BY 一起工作。

标签: neo4j cypher


【解决方案1】:

对于简单的关系表达式,Cypher 通过强制执行 uniqueness 自动排除多个关系:

在模式匹配时,Neo4j 确保不包含在单个模式中多次找到相同图形关系的匹配项。

文档并不完全清楚这是否适用于可变长度路径 - 所以让我们设计一个小实验来确认它:

CREATE
  (n1:Node {name: "n1"}),
  (n2:Node {name: "n2"}),
  (n3:Node {name: "n3"}),
  (n4:Node {name: "n4"}),
  (n1)-[:REL]->(n2),
  (n2)-[:REL]->(n3),
  (n3)-[:REL]->(n2),
  (n2)-[:REL]->(n4)

结果如下图:

查询:

MATCH (n:Node {name:"n1"})-[:REL*..]->(m)
RETURN m

结果是:

╒══════════╕
│m         │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n2}│
├──────────┤
│{name: n4}│
├──────────┤
│{name: n4}│
└──────────┘

如您所见,n4 包含多次(因为它可以通过避免循环和通过循环来访问)。 用PROFILE检查执行:

所以我们应该使用DISTINCT 来消除重复项:

MATCH (n:Node {name:"n1"})-[:REL*..]->(m)
RETURN DISTINCT m

结果是:

╒══════════╕
│m         │
╞══════════╡
│{name: n2}│
├──────────┤
│{name: n3}│
├──────────┤
│{name: n4}│
└──────────┘

再次,用PROFILE检查执行:

【讨论】:

  • 至少在我的查询中的结果,当我将深度减少到例如6 有多次同一个节点。
  • 当我删除不同的节点时,有多次相同的节点..所以查询需要很长时间。
  • 你说得对,我在实验上做了一些工作并相应地更新了我的答案。
  • 顺便说一句,查询完成后(2小时内),你得到了多少个节点?
  • 感谢您的帮助。结果几乎是完整图的一半,大约 25.000 个节点。如果我能找到一个 cypher 只会对每个节点展开一次的表达式,我想它会变得更快。
【解决方案2】:

我们当然可以做一些事情来改进这个查询。

首先,您根本没有使用标签。而且由于您没有使用标签,因此输入节点上的匹配项无法利用您可能拥有的任何模式索引,并且它必须扫描所有 50k 节点访问和比较属性,直到找到每个具有给定的标题和命名空间(找到一个它不会停止,因为它不知道是否有其他节点满足条件)。您可以通过仅在起始节点上匹配来检查时间。

为了改善这一点,你的节点应该被标记,你的起始节点上的匹配应该包括标签,你的 title 和 namespaceID 属性应该被索引。

仅此一项就可以显着提高查询速度。

下一个问题是剩余的瓶颈更多是由于排序,还是返回大量结果集?

您可以通过限制返回的结果来单独检查排序的成本。

您可以在匹配后的查询结束时使用它。

WITH DISTINCT n
ORDER BY n.title
LIMIT 10
RETURN n.title AS Title, n.namespaceID 
ORDER BY n.title

此外,在进行任何性能调整时,您应该对您的查询进行概要分析(至少是那些在合理时间内完成的查询)并解释那些花费大量时间来检查查询计划的查询。

【讨论】:

  • 感谢您的回答。我使用了最多 10 个的限制(摩尔以 [:categorieLinkTo*..10] 的方式使用,但我认为它是相同的并且没有明显区别,我得到了超过 55.000 个节点。我所有的节点都被标记但它们都保持相同的标签(因为数据库稍后会变得更大..),所以主要问题是,查询没有意识到,它已经访问了一些节点。但是我现在自己正在研究一个简单的 BFS,因为 Cypher 没有提供任何有用的东西在这里。
  • LIMIT 10 的不同之处在于它只会返回 10 个结果。建议是测试查询是否仅因为返回的大量行(返回这么多似乎没有意义)而导致查询效率低下,或者是否还有其他主要的低效率也导致大幅减速,我认为有。
  • 再一次,如果您打算使用索引来查找起始节点,则需要在查询中添加标签。就目前而言,它正在对所有 55k 个节点进行完整的图形扫描和属性比较,只是为了找到节点来开始其余的查询。您可以通过查看执行需要多长时间来测试它:MATCH (m {title:{title},namespaceID:{namespaceID}}) 您也可以尝试对其进行 PROFILE
【解决方案3】:
public static HashSet<Node> breadthFirst(String name, int namespace, GraphDatabaseService db) {

        // Hashmap for storing the cypher query and its parameters
        Map<String, Object> params = new HashMap<>();


        // Adding the title and namespaceID as parameters to the Hashmap
        params.put("title", name);
        params.put("namespaceID", namespace);

        /*it is a simple BFS with these variables below
         * basically a Queue (touched) as usual, a Set to store the nodes
         *  which have been used (finished), a return variable and 2 result
         *  variables for the queries
         */
        Node startNode = null;
        String query = "Match (n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo]-> (m) RETURN m";
        Queue<Node> touched = new LinkedList<Node>();
        HashSet<Node>finished = new HashSet<Node>();
        HashSet<Node> returnResult = new  HashSet<Node>();

        Result iniResult = null;
        Result tempResult=null;

        /*the part below get the direct nodes and puts them
         * into the queue
         */
            try (Transaction tx = db.beginTx()) {
                 iniResult =db.execute(query,params);


                while(iniResult.hasNext()){
                  Map<String,Object> iniNode=iniResult.next();
                  startNode=(Node) iniNode.get("m");
                  touched.add(startNode);
                  finished.add(startNode);
                }
                tx.success();
                }catch (QueryExecutionException e) {
                logger.error("Fehler bei Ausführung der Anfrage", e);  
                }

            /*and now we just execute the BFS (don't think i need more to
             * say here.. we are all pros ;))
             * as usual, marking every node we have visited
             * and saving every visited node.
             * the difficult part had been the casting from
             * and to node and result, everything else is pretty much
             * straightforward. I think the  variables explain their self
             * via their name....
             */

               while(! (touched.isEmpty())){
                 try (Transaction tx = db.beginTx()) {
                   Node currNode=touched.poll();
                   returnResult.add(currNode);

                   tempResult=null;               
                   Map<String, Object> paramsTemp = new HashMap<>();
                   paramsTemp.put("title",currNode.getProperty("title").toString());
                   paramsTemp.put("namespaceID", 14);
                   String tempQuery = "MATCH (n{title:{title},namespaceID:{namespaceID}})-[:categorieLinkTo] -> (m) RETURN m";
                   tempResult =  db.execute(tempQuery,paramsTemp);



                 while(tempResult.hasNext()){
                     Map<String, Object> currResult= null;
                     currResult=tempResult.next();
                     Node tempCurrNode = (Node) currResult.get("m");

                     if (!finished.contains(tempCurrNode)){
                         touched.add(tempCurrNode);
                         finished.add(tempCurrNode);


                     }


                  }


               tx.success();  
            }catch (QueryExecutionException f) {
                logger.error("Fehler bei Ausführung der Anfrage", f);
            }
        }       


        return returnResult;

}   

由于我找不到合适的 Cypher 表达式,所以我自己写了一个漂亮的 BFS - 看起来很有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-19
    • 1970-01-01
    • 2021-10-18
    • 1970-01-01
    • 2016-06-23
    • 1970-01-01
    相关资源
    最近更新 更多