Neo4j Cypher - 如何查找与节点列表有关系的所有节点

8

我有一些被命名为“选项”的节点,用户可以选择这些选项。我需要一个密码查询,它的工作方式如下:

检索选择了所有给定列表中选项的用户。

MATCH (option:Option)<-[:CHOSE]-(user:User) WHERE  option.Key IN ['1','2','2'] Return user

这个查询会给我返回选择了选项(1)、(2)和(3)的用户以及只选择了选项(2)的用户。

我需要的是只有选择了全部三个选项 - 选项(1)、选项(2)和选项(3)的用户。

4个回答

10

对于一个全密钥的解决方案(不知道是否比Chris'的答案更好,你需要测试和比较),你可以收集每个用户的option.Key,并过滤掉那些在列表中没有option.Key的值。

MATCH (u:User)-[:CHOSE]->(opt:Option)
WITH u, collect(opt.Key) as optKeys
WHERE ALL (v IN {values} WHERE v IN optKeys)
RETURN u

或者匹配所有在您列表中的键和选择它们的用户的选项,按用户收集这些选项,并将选项集合的大小与您的列表大小进行比较(如果您的列表中没有重复项,则具有相等大小的选项集合的用户选择了所有选项)

MATCH (u:User)-[:CHOSE]->(opt:Option)
WHERE opt.Key IN {values}
WITH u, collect(opt) as opts
WHERE length(opts) = length({values}) // assuming {values} don't have duplicates
RETURN u

此查询应该将结果限制为与所有选项相关联的用户,选项的键值在{values}中指定,您可以改变集合参数的长度而不改变查询。


我认为第二种方法更好。因为用户的选择可能非常多,但选项列表最多只有20个左右。谢谢。实际上,两种答案都可以。我会使用性能更高的那个。 - hisarkaya

2
如果选项数量有限,您可以这样做:
MATCH 
    (user:User)-[:Chose]->(option1:Option),
    (user)-[:Chose]->(option2:Option),
    (user)-[:Chose]->(option3:Option)
WHERE
    option1.Key = '1'
    AND option2.Key = '2'
    AND option3.Key = '3'
RETURN
    user.Id

这将仅返回具有所有3个选项的用户。

显然,这样做会导致您拥有3行而不是1行,但我不知道如何使用IN关键字来实现您想要的效果。

如果您正在对其进行编码,则生成WHEREMATCH子句非常简单,但仍然不理想。 :(

编辑 - 示例

事实证明这里进行了一些字符串操作(!),但您始终可以缓存位。重要的是 - 它使用Params,这将允许neo4j缓存查询并在每次调用时提供更快的响应。

public static IEnumerable<User> GetUser(IGraphClient gc)
{
    var query = GenerateCypher(gc, new[] {"1", "2", "3"});
    return query.Return(user => user.As<User>()).Results;
}


public static ICypherFluentQuery GenerateCypher(IGraphClient gc, string[] options)
{
    ICypherFluentQuery query = new CypherFluentQuery(gc);
    for(int i = 0; i < options.Length; i++)
        query = query.Match(string.Format("(user:User)-[:CHOSE]->(option{0}:Option)", i));

    for (int i = 0; i < options.Length; i++)
    {
        string paramName = string.Format("option{0}param", i);
        string whereString = string.Format("option{0}.Key = {{{1}}}", i, paramName);
        query = i == 0 ? query.Where(whereString) : query.AndWhere(whereString);
        query = query.WithParam(paramName, options[i]);
    }

    return query;
}

我的选项列表将是动态的,因此我不知道选项列表的实际大小。 - hisarkaya
你将使用什么与服务器进行编程交互?我认为你最终可能会受到生成“Where”和“Match”语句的限制。 - Charlotte Skardon
我正在使用 .Net Web Api。因此,请求将作为 json 选项 id 列表。在服务器上,我使用 Neo4jClient。 - hisarkaya
那么你的意思是我应该使用字符串拼接等创建动态密码。好的,我会尝试一下。但这是否是正确的方法呢? - hisarkaya
谢谢Chris。它解决了我的问题。如果查询性能良好,那么一切都很好。 - hisarkaya
显示剩余3条评论

2
MATCH (user:User)-[:CHOSE]->(option:Option) 
WHERE option.key IN ['1', '2', '3']
WITH user, COUNT(*) AS num_options_chosen
WHERE num_options_chosen = LENGTH(['1', '2', '3'])
RETURN user.name

此查询仅返回与给定数组中所有选项具有关系的用户。 这假设用户和选项之间没有多个[:CHOSE]关系。 如果一个用户可能与单个选项有多个[:CHOSE]关系,则必须根据需要添加一些条件语句。

我使用以下数据集测试了上述查询:

CREATE (User1:User {name:'User 1'}),
       (User2:User {name:'User 2'}),
       (User3:User {name:'User 3'}),

       (Option1:Option {key:'1'}),
       (Option2:Option {key:'2'}),
       (Option3:Option {key:'3'}),
       (Option4:Option {key:'4'}),

       (User1)-[:CHOSE]->(Option1),
       (User1)-[:CHOSE]->(Option4),

       (User2)-[:CHOSE]->(Option2),
       (User2)-[:CHOSE]->(Option3),

       (User3)-[:CHOSE]->(Option1),
       (User3)-[:CHOSE]->(Option2),
       (User3)-[:CHOSE]->(Option3),
       (User3)-[:CHOSE]->(Option4)

我只得到了“用户3”作为输出结果。

谢谢Nicole,这是一个和jjaderberg的答案不同的观点。但没关系,没问题。 - hisarkaya
啊,他一定是在我打字的时候提交了那个。绝对是一个好的解决方案。 - Nicole White

2

对于较短的列表,您可以在WHERE子句中使用路径谓词:

MATCH (user:User)
WHERE (user)-[:CHOSE]->(:Option { Key: '1' })
AND   (user)-[:CHOSE]->(:Option { Key: '2' })
AND   (user)-[:CHOSE]->(:Option { Key: '3' })
RETURN user

优点:

  • 易于阅读
  • 适用于动态长度列表的生成

缺点:

  • 每个不同长度的列表,都需要一个不同的查询进行解析和由Cypher缓存。太多动态查询会导致缓存命中率下降、查询编译工作量增加、查询性能降低。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接