Displaying Relationships in Neo4j - neo4j

I wrote a CQL, and i am getting the result in the tabular format, now i want to show it graphically.
Here is the code in CQL:
match (tc:TeamCoaches)-[:BELONGS_TO]->(t:Teams)
with t.TeamName as TeamNames,
count(distinct tc.CoachName) as no_of_coach,
collect (tc.CoachName) as Coachnames
where no_of_coach>= 2
return TeamNames, no_of_coach,Coachnames
Actual Results:
TeamNames no_of_coach Coachnames
A 2 [P,Q]
B 3 [X,Y,Z]
Expected Results: Should be in graphical form.
Example:
(P)------------->(A)<---------------(Q)
BELONGS_TO BELONGS_TO
BELONGS_TO BELONGS_TO
(X)------------->(B)<--------------->(Y)
^
|
|BELONGS_TO
|
(Z)

You need to return nodes instead of their properties(here you are returning names) to show in the graph form.
You can modify your query to show in graph form as :
MATCH (tc:TeamCoaches)-[:BELONGS_TO]->(t:Teams)
WITH t, count(distinct tc) as no_of_coach
WHERE no_of_coach>= 2
MATCH P=(tc:TeamCoaches)-[:BELONGS_TO]->(t)
RETURN P

Related

Neo4j Match with multiple relationships

I need a MATCH where either relationship is true. I understand the (person1)-[:r1|:r2]-(person2). The problem I am having is that one of the MATCH traverse through another node. IE:
(p1:person)-[:FRIEND]-(p2:person)-[:FRIEND]-(p3:person)
So I want this kind of logic. The enemy of my enemy is my friend. And my friend is my friend. Output list of all the names who are my friend. I also limit the relationship to a particular value.
Something like:
MATCH (p1:Person)-[:ENEMY{type:'human'}]-(myEnemy:Person)-[enemy2:ENEMY{type:'human'}]-(myFriend:Person)
OR (p1:Person)-[friend:FRIEND{type:'human'}]-(myFriend:Person)
RETURN p1.name, myFriend.name
I need one list that I can then do aggregation on.
This is my first posting....so if my question is a mess...hit me with your feedback and I will clarify :)
You can use the UNION clause to combine 2 queries and also remove duplicate results:
MATCH (p:Person)-[:ENEMY{type:'human'}]-(:Person)-[:ENEMY{type:'human'}]-(f:Person)
WHERE ID(p) < ID(f)
RETURN p.name AS pName, f.name AS fName
UNION
MATCH (p:Person)-[:FRIEND{type:'human'}]-(f:Person)
WHERE ID(p) < ID(f)
RETURN p.name AS pName, f.name AS fName
The ID(p) < ID(f) filtering is done to avoid having the same pair of Person names being returned twice (in reverse order).
[UPDATE]
To get a count of how many friends each Person has, you can take advantage of the new CALL subquery syntax (in neo4j 4.0) to do post-union processing:
CALL {
MATCH (p:Person)-[:ENEMY{type:'human'}]-(:Person)-[:ENEMY{type:'human'}]-(f:Person)
WHERE ID(p) < ID(f)
RETURN p.name AS pName, f
UNION
MATCH (p:Person)-[:FRIEND{type:'human'}]-(f:Person)
WHERE ID(p) < ID(f)
RETURN p.name AS pName, f
}
RETURN pName, COUNT(f) AS friendCount

Neo4j cypher ALL IN

I have the following Cypher query:
MATCH (genre:Genre)<-[:BELONGS_TO]-(t:Title)
WHERE genre.name IN ["Comedy", "Drama"]
RETURN t
Which returns titles that belong to Comedy OR Drama genres.
How to change this query in order to return all titles that belong to Comedy AND Drama genres?
SIZE is your friend.
MATCH (t:Title)-[:BELONGS_TO]->(g:Gender)
WHERE g.name IN ["Comedy", "Drama"]
WITH t, COLLECT(g) AS g
WHERE SIZE(g) >= x
RETURN t
x - is number of elements in IN clause

Can I find a bookshelf that has book A and book (B, C, or D) in one activerecord query?

Imagine these associations:
class Bookshelf
has_many :book_associations, dependent: :destroy
has_many :books, through: :book_associations
end
class Book
has_many :book_associations, dependent: :destroy
has_many :bookshelves, through: :book_associations
end
class BookAssociation
belongs_to :book
belongs_to :bookshelf
end
I need to find all bookshelves that contain a book with ID A and a book with ID B, C, or D
I can do this in a multi-step process using ruby like:
bookshelf_ids1 = Book.find(A).bookshelves.pluck(:id)
bookshelf_ids2 = Book.where(id: [B, C, D]).map(&:bookshelves).flatten.uniq.pluck(:id)
Bookshelf.where(id: bookshelf_ids1 & bookshelf_ids2)
But there must be a way to do this in one line, either through ActiveRecord or a raw SQL query.
What this question boils down to is that you're looking for an intersection of Bookshelf objects in set A (contains book with ID a) and Bookshelf objects in set B (contains book with ID in array b).
I don't recall seeing any easy way to express this intersection using a single ActiveRecord query. And as you probably have suspected, a multi query approach wouldn't scale well. Why run three queries when you can run one?
So here's my solution:
Finding the Bookshelve IDs
SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = 1 AND b2.book_id IN (2,3,4);
This is a bit complicated so let me break it down. We are joining the book_associations table on itself, on its own bookshelf_id. This has the effect of making two tables available for filtering. We then filter query table b1 for the first criteria (ID = 1), and filter query table b2 for the other criteria (ID in (2,3,4)). With the INNER JOIN we are then ensuring that we are only getting the intersection of tables b1 and b2. We retrieve only the bookshelf_id because we're looking only to retrieve the bookshelves. Finally, the DISTINCT query is the SQL equivalent of .uniq and ensures the returned values are unique.
Retrieving the Bookshelves
From here, we then need to instantiate the Bookshelf objects. While we could do this:
bookshelf_ids = ActiveRecord::Base.connection.query(["SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = ? AND b2.book_id IN (?);", 1, [2,3,4]])
bookshelves = Bookshelf.find(bookshelf_ids)
It's still two steps. Here's a single-step solution:
Bookshelf.find_by_sql(["SELECT * FROM bookshelves bs
WHERE bs.id IN (SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = ? AND b2.book_id IN (?)
)", first_id,second_ids])
The Bookshelf.select_by_sql command instantiates records from the query results. The bookshelf_ids are retrieved in the subquery and used as a condition for the query on the bookshelves table.
Caveats
I haven't tested this code and can't confirm it will work, but the broad strokes should be correct.
The SQL should be valid for PostgresQL, but may require some tweaks depending on your specific DB implementation.
Edit
I've corrected the code above, I had joined the book_associations table on the wrong column (id) instead of the correct column bookshelf_id, and the subquery was returning the wrong column (again id when it should've been bookshelf_id).
I've created a proof of concept with test included.

ActiveRecord: get only one record using has_one at children from grandparent at some conditions

For example I have three table named A B and C. A can have many B and B can have many C. At only one condition A.example_condition = 1 then A will have only one B and B has only one C.
I want to get directly C from A from that case. So I try this:
class A < ApplicationRecord
has_one :special_c -> {joins("INNER JOIN B ON B.id = C.b_id INNER JOIN A ON A.id = B.a_id")
.where("A.example_condition = 1")}, class_name: "C"
end
But when I run this above query, ActiveRecord will automatically insert statement after where clause:
A.id = C.a_id
Obviously C table doesn't have column a_id (it is stored in B table). My question is: How can I improve above query for my purpose or some other better way for this problem.
thanks

Rails: Find entity by many-to-many association

I have a model A, and a model B. A has_and_belongs_to_many Bs, and vice versa.
Now, I want to find an object/entity within A that has_and_belongs_to certain objects within B (say B1 and B2). How can I do that efficiently within Rails? My current solution is something like this:
A.all.select {|a| a.bs.sort == [B1, B2]}.first
It basically iterates through all objects within A and checks if it has_and_belongs_to the correct Bs. That is very inefficient. Is there a better way to do this?
You can do this with nested sub-queries, which is a working solution but not necessarily an efficient one, so you'll have to run some benchmarks.
The following involves three nested queries performed on the join_table between A and B.
You first determine the id's of all B's (call these excluded_bs) that are not either B1 or B2. Then, you determine which A's are in a relationship with these excluded_bs and call them excluded_as. All the A's that are not in excluded_as are exactly the ones we want (call them included_as). Once you have included_as just query the A table.
excluded_bs = %(SELECT B_id FROM join_table WHERE B_id NOT IN (:included_bs))
excluded_as = %(SELECT A_id FROM join_table WHERE B_id IN (#{excluded_bs}))
included_as = %(SELECT A_id FROM join_table WHERE A_id NOT IN (#{excluded_as}))
A.where("id IN (included_as)", :included_bs => [B1.id, B2.id])
This should give you all the A's that are in a relationship with exactly B1 and B2, but not with any others. You might be able to clean this up a bit and make it more efficient, but it should at least work.
EDIT:
Whoops! To trim off those that only have either B1 or B2, try a GROUP BY. Change the last sub-query to
included_as = %(SELECT A_id, COUNT(*) as Total FROM join_table WHERE A_id NOT IN (#{excluded_as}) GROUP BY A_id HAVING Total = :count)
and the main query to
Bs = [B1, B2]
A.where("id IN (SELECT A_id FROM (#{included_as}))", :included_bs => Bs.map(&:id), :count => Bs.count)
You can filter on habtm associations:
A.joins(:bs).where("bs.id" => [B1, B2]).first
A.joins(:bs).where("bs.id" => [B1, B2]).all
To ensure that only the items with exactly two associations are returned, use
A.joins(:bs).where("bs.id" => [B1, B2]).group("as.id HAVING COUNT(bs.id) = 2")

Resources