Find “friends of friends” with OrientDB SQL

最后都变了- 提交于 2019-12-02 06:58:21

问题


Although one of the most common use cases for demonstrating the capabilities of a graph database, I cannot seem to find a good example or best practice for obtaining "friends of friends" with OrientDB SQL.

Lets assume a social network and try to model it with "user" vertices and "is_friend_with" edges.


Definition:

Vertex class user with properties uuid (custom unique id) and name

Edge class is_friend_with with property status which can be "pending" or "approved"

Users are connected to each other with unidirectional edges. The direction doesn't really matter; as long as status="approved", those two users are friends.


This is one solution I came up with:

select from (
    select expand($all) let
        $a = (select expand(outE('is_friend_with')[status='approved'].inV('user').outE('is_friend_with')[status='approved'].inV('user')) from (select from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24')),
        $b = (select expand(outE('is_friend_with')[status='approved'].inV('user').inE('is_friend_with')[status='approved'].outV('user')) from (select from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24')),
        $c = (select expand(inE('is_friend_with')[status='approved'].outV('user').inE('is_friend_with')[status='approved'].outV('user')) from (select from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24')),
        $d = (select expand(inE('is_friend_with')[status='approved'].outV('user').outE('is_friend_with')[status='approved'].inV('user')) from (select from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24')),
        $all = unionall($a, $b, $c, $d)
) where uuid <> '95920a96a60c4d40a8f70bde98ae1a24'

(The user with uuid='95920a96a60c4d40a8f70bde98ae1a24' is the starting point.)

But, I don't find it very elegant. Some of the problems that I can spot immediately are:

  • Repetition of select from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24'. Unfortunately, I couldn't find a way to assign it to a variable and then use it in the "from" clause
  • I was forced to make all the combinations of incoming/outgoing edges/vertices instead of using both(), since I want to check every edge for status="approved"
  • This query also returns the direct friends, instead of friends of friends only

I tried to use traverse, but to no avail (again, didn't find a way how to check the edges for status="approved" while traversing).

Could you, please, propose some OSQL solution for this problem? Thanks in advance.


回答1:


There may be a nicer way to do it, but I think this gives you what you're looking for:

Setup:

create class User extends V
create class IsFriendsWith extends E
create property User.name string
create property User.uuid string
create property IsFriendsWith.status string

create vertex User set uuid = "1", name = "Bob"
create vertex User set uuid = "2", name = "Sally"
create vertex User set uuid = "3", name = "Eric"
create vertex User set uuid = "4", name = "Jenny"
create vertex User set uuid = "5", name = "Dennis"
create vertex User set uuid = "6", name = "Mary"
create vertex User set uuid = "7", name = "John"

create edge IsFriendsWith from (select from User where uuid = "1") to (select from User where uuid = "2") set status = "approved"
create edge IsFriendsWith from (select from User where uuid = "1") to (select from User where uuid = "3") set status = "pending"
create edge IsFriendsWith from (select from User where uuid = "2") to (select from User where uuid = "4") set status = "approved"
create edge IsFriendsWith from (select from User where uuid = "5") to (select from User where uuid = "2") set status = "pending"
create edge IsFriendsWith from (select from User where uuid = "3") to (select from User where uuid = "4") set status = "approved"
create edge IsFriendsWith from (select from User where uuid = "6") to (select from User where uuid = "1") set status = "approved"
create edge IsFriendsWith from (select from User where uuid = "6") to (select from User where uuid = "7") set status = "approved"

Query:

select from (
    select 
      expand(unionall(
        outE("IsFriendsWith")[status="approved"].inV(), 
        inE("IsFriendsWith")[status="approved"].outV()
      ))
      from (
        select 
            expand(unionall(
              outE("IsFriendsWith")[status="approved"].inV(), 
              inE("IsFriendsWith")[status="approved"].outV()
            ))
        from (
          select from User where uuid = "1"
        )
      )
  )
) where uuid <> "1"

Enrico's answer won't give you quite what you're after because it only takes into account edges in one direction when it needs to work both ways.

Edit: To exclude people that the user happens to be friends with already, use the following (note that this example assumes the User's RID is #26:0)

  select from (
      select 
        expand(unionall(
          outE("IsFriendsWith")[status="approved"].inV(), 
          inE("IsFriendsWith")[status="approved"].outV()
        ))
        from (
          select 
              expand(unionall(
                outE("IsFriendsWith")[status="approved"].inV(), 
                inE("IsFriendsWith")[status="approved"].outV()
              ))
          from #26:0          
        )
    )
  )
  where @rid <> #26:0 and @rid NOT IN (select both("IsFriendsWith") from #26:0)

Edit 2: Using variables instead.

 select from (
      select 
        expand(unionall(
          outE("IsFriendsWith")[status="approved"].inV(), 
          inE("IsFriendsWith")[status="approved"].outV()
        ))
        from (
          select 
              expand(unionall(
                outE("IsFriendsWith")[status="approved"].inV(), 
                inE("IsFriendsWith")[status="approved"].outV()
              ))
          from (
             select expand($u) let $u = first((select from User where uuid = "1"))
          )
        )
    )
  )
  where @rid <> $u and @rid NOT IN $u.both("IsFriendsWith")



回答2:


This should be much simpler:

select expand(
  bothE('is_friend_with')[status = 'approved'].bothV()
  .bothE('is_friend_with')[status = 'approved'].bothV()
) from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24'

With bothE() and then bothV() you get both in/out connections at edge/vertex level.

In order to exclude current user you could use:

select expand(
  bothE('is_friend_with')[status = 'approved'].bothV()
  .bothE('is_friend_with')[status = 'approved'].bothV()
  .remove( @this )
) from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24'



回答3:


Have you tried this?

select 
      expand(bothE('is_friend_with')[status = 'approved'].inV().bothE('is_friend_with')[status = 'approved'].inV()) 
from user where uuid = '95920a96a60c4d40a8f70bde98ae1a24'


来源:https://stackoverflow.com/questions/28685205/find-friends-of-friends-with-orientdb-sql

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!