neo4j Multiple optional paths - cypher - neo4j

I'm tracking if a user has liked and or voted on a object in a list of objects others posted.. I can get either likes and votes, but not both. (A person can both like and vote on an object and these options are not mutually exclusive).
To simply this problem let me describe it in relational terms (left joins used - object is ALWAYS returned, liker and voter data is only returned if a record of that type exists)
[object]+ -> liker
+ -> voter
What I'd like to return is:
objectID likerID voterID
2343 null 88
2345 11 null
2382 44 1256
2400 null null
Yet every which way I've sliced I cannot get it to come out like that . Either row 2400 is skipped (I've tried every combination of where), or values are even shifted from likerID to the voterID column (bug?).
Here is a sample of the cypher:
start objects=node(158)
match contestant-[:POSTED]->object_node-[:POSTED_OBJECT]->objects<-[?:POSTED_OBJECT]-object_node_a<-[?:LIKES]-liker
, objects<-[?:POSTED_OBJECT]-object_node_b<-[?:VOTES]-voter
return id(object, id(liker), id(voter)
It doesn't work even if I try where id(object_node_a) = id(object_node_b)...
If I just try to get a liker it works.. same with voter.. but when I try to do both.. bombs..
I've tried using where , etc but ultimately I never get the full list of objects - it either trims down the list based upon matches, or gives me the Cartesian product which distinct does not resolve.
SQL EXAMPLE: LEFT JOIN
I'm a sql guy so let me explain it this way - I have a objects table on the left, and I want to left join it to a liker table and a voter table, and return both the liker id and voter id on a single row along with the object data. All the object records will be returned regardless if there is a voter or liker record.
[object]+ -> liker
+ -> voter
IS THIS EVEN POSSIBLE?
Is it possible to do this via cypher?

Hopefully I haven't misunderstood. To get
objectID likerID voterID
2343 null 88
2345 11 null
2382 44 1256
2400 null null
i.e. all objects and the ID of those that liked it and voted for it, this query should do it-
start o=<lookup for objects>
match ul-[like?:LIKED]->o, uv-[vote?:VOTED]->o
return o,ID(ul),ID(uv)
This will return objects that no votes and likes, both votes and likes and either one. Note that if you have multiple users voting for the same object as is likely, then your object row will repeat for each user. You might want to do something like
start o=<lookup for objects>
match ul-[like?:LIKED]->o, uv-[vote?:VOTED]->o
return o,collect(ID(ul)),collect(ID(uv))
to still get a row per object but a collection of user IDS for votes and likes.
To include the person that posted the object as well:
start o=node(4,5,6,7)
match ul-[like?:LIKED]->o, uv-[vote?:VOTED]->o, c-[:POSTED_OBJECT]->o
return o,ID(ul),ID(uv),ID(c)
I created a tiny sample to play with: http://console.neo4j.org/r/in8g4w

Related

Return children of Realm RLMResults<Object> instead of <Object>'s when filtering

Example Realm relationship:
People.Dogs.FavouriteFoods
that are strictly one way -> RLMArrays
I have:
let result = RLMResult<People> from a previous operation.
and I have an array of FavouriteFood.IDs that a user selected
let selectedIDs: [String]
Now I am trying to filter/predicate this result, but instead of returning People, which I already have, I am trying to get out the FavouriteFood objects that intersect with the selectedIDs I can only find guides that explain how to sort/filter on RLMResults<People> where the result is People i.e. the same as the generic type on RLMResult.
My goal is to, in the end, construct a list where I can say "Out of the 14 FavouriteFoods Person A's Dogs have, 7 of them are in the selectedIDs list" etc. for Person B, C, D...
I want something like: "ANY dogs.favouriteFoods.ID in selectedIDs" but it should return all the FavouriteFoods matching the predicate for an individual Person, instead of all the People having Dogs having these particular favouriteFoods.
Is this possible to do as a predicate? Is there a way to flip the concept to ask for FavouriteFoods instead, or must I loop over all people, dogs, favouriteFoods and manually tally this up?
Thanks for any help given.

neo4j - Return single instance of node - querying by property?

I am building a social network that has a specialized audience.
Users are related to each other by three primary relationship types.
[:FRIENDS]->(:USER),
[:WORKS_AT]->(:COMPANY),
[:WORKED_AT]->(:COMPANY),
[:FOLLOWS].
When working through a search scenario (a user wants to find another user), I've given each relationship a "priority" (so to speak).
For example, if a user wants to find another user named "Bart Simpson" - first, we will check co-worker relationships ([:WORKS_AT],[:WORKED_AT]). I've assigned those relationships a priority of 1. That way, "Bart Simpson" who works with me will appear in the search results before "Bart Simpson" - who lives hundreds of miles away in Springfield.
The second priority is [:FRIENDS]->(:USER). Do any of my friends have a friend named "Bart Simpson?" Priority #2.
The last priority is a global search. I don't have any co-workers named "Bart Simpson", my friends don't have any friends named "Bart Simpson" - but I met Bart at a conference, and I want to "friend" him. So, I've added a "Global" search. Find any users named "Bart Simpson".
So far, this is my Cypher:
optional match (u:USER {id:'1'})-[:WORKS_AT|:WORKED_AT]-(w:COMPANY)-[r]-(f:USER)
with collect(f{.*, priority:1,relationship:r.title,type:type(r)}) as user
optional match (u:USER {id: '1'})-[:FRIENDS]-(:USER)-[r:FRIENDS]-(f:USER)
with user + collect(f{.*, priority:2,relationship:r.title,type:type(r)}) as user
optional match (f:USER)
where f.id <> '1'
with user + collect(f{.*, priority:3,relationship:'',type:''}) as user
unwind user as users
with users as user
where toLower(user.last_name) STARTS WITH toLower('Sc') OR toLower(user.first_name) STARTS WITH toLower('Sc')
return distinct user
This is fantastic - however, a user could work at the same company, as well as
be friends, as well as appear in the global search. So - we have the potential for three (or more) "copies" of the same user - with different relationship attributes. The relationship attributes are important because in the app, they provide important context to the search. "Bart Simpson - Works at XYZ Company."
So what I'm really looking for is the ability to either return the user record with the highest priority - and do that based on the "ID" field. If that doesn't work, I could see a situation where we try to update the property of a node. So, when the query hits the priority 2 search, if there is already a user in the collection with the same "ID", it just appends the P2 relationship type to the record. Either is fine with me.
I'm open to suggestions and listening!
So, I've made some progress!
MATCH
(subject:USER {id:'1'})
MATCH
(subject)-[:WORKS_AT|:WORKED_AT]-(w:COMPANY)-[r]-(f1:USER)
WHERE
toLower(f1.last_name) STARTS WITH toLower('Sc') or
toLower(f1.first_name) STARTS WITH toLower('Sc')
WITH
COLLECT(f1.id) AS userIds,
COLLECT(f1{.*,priority:1,rType:type(r), title:r.title, detail:w.name}) AS users
OPTIONAL MATCH
(subject)-[:FRIEND]-(fw:USER)-[r:FRIEND]-(f2:USER)
WHERE
NOT(f2.id in userIds) AND
(
toLower(f2.last_name) STARTS WITH toLower('Sc') or
toLower(f2.first_name) STARTS WITH toLower('Sc')
)
WITH
users + COLLECT(f2{.*,priority:2,rType:"FRIEND", title:"Friends with " + fw.first_name + " " + fw.last_name, detail:''}) AS users,
userIds + collect(f2.id) AS userIds
OPTIONAL MATCH
(f3:USER)
WHERE
NOT(f3.id in userIds) AND
(
toLower(f3.last_name) starts with toLower('Sc') OR
toLower(f3.first_name) starts with toLower('Sc')
)
WITH
users + COLLECT(f3{.*,priority:3,rType:"GLOBAL", title:"", detail:''}) AS users
RETURN
users
The query has evolved a bit. Essentially, at the first stage, we collect the userIds of the items that were returned. At each subsequent stage, the results returned are compared against the running list of ids. If the id of the result is already in the list of ids, it is filtered out - thus ensuring a unique id in the set.
This is working - and for now, I'm going to run with it. Is this the most efficient query, or is there a better way to deal with this scenario?

Returning like count and whether current user liked a post in Cypher

I have a possibly bone-headed question, but I'm just starting out with Neo4j, and I hope someone can help me out with learning Cypher syntax, which I've just started learning and evaluating.
I have two User nodes, and a single NewsPost node. Both users LIKE the NewsPost. I'm able to construct a Cypher query to count the likes for the post, but I'm wondering if it's also possible to check if the current user has liked the post in the same query.
What I have so far for a Cypher query is
match (p:NewsPost)<-[r:LIKES]-(u:User)
where id(p) = 1
return p, count(*)
Which returns the post and like count, but I can't figure out the other part of "has the current user liked this post". I know you're not supposed to filter on <id>, but I learned that after the fact and I'll go back and fix it later.
So first, is it possible to answer the "has the current user liked this post" question in the same query? And if so, how do I modify my query to do that?
The smallest change to your query that adds a true/false test for a particular user liking the news post would be
MATCH (p:NewsPost)<-[r:LIKES]-(u:User)
WHERE ID(p) = 1
RETURN p, count(r), 0 < size(p<-[:LIKES]-(:User {email:"michael#nero.com"}))
This returns, in addition to your query, the comparison of 0 being less than the size of the path from the news post node via an incoming likes relationship to a user node with email address michael#nero.com. If there is no such path you get false, if there is one or more such paths you get true.
If that does what you want you can go ahead and change the query a little, for instance use RETURN ... AS ... to get nicer result identifiers, and so on.
What you are looking for is Case.
In your database you should have something unique for each user (id property, email or maybe login, I don't know), so you have to match this user, and then match the relation to the post you want, using case you can return a boolean.
Example:
Optional Match (u:User{login:"Michael"})-[r:LIKES]-(p:newPost{id:1})
return CASE WHEN r IS NULL THEN false ELSE true END as userLikesTopic
If you want to get the relation directly (to get a property in it as example) you can remove the CASE part and directly return r, if it does not exist, null will be returned from the query.

Auto-assigning objects to users based on priority in Postgres/Ruby on Rails

I'm building a rails app for managing a queue of work items. I have several types of users ("access levels") to whom I want to auto-assign these work items.
The end goal is an "Auto-assign" button on one of my views that will automatically grab the next work item based on a priority, which is defined by the users's access level.
I'm trying to set up a class method in my work_item model to automatically sort work items by type based on the user's access level. I am looking at something like this:
def self.auto_assign_next(access_level)
case
when access_level = 2
where("completed = 'f'").order("requested_time ASC").limit(1)
when access_level > 2
where("completed = 'f'").order("CASE WHEN form='supervisor' THEN 1 WHEN form='installer' THEN 2 WHEN form='repair' THEN 3 WHEN form='mail' THEN 4 WHEN form='hp' THEN 5 ELSE 6 END").limit(1)
end
This isn't very DRY, though. Ideally I'd like the sort order to be configurable by administrators, so maybe setting up a separate table on which the sort order is kept would be best. The problem with that idea is that I have no idea how to pass the priority order on that table to the [postgre]SQL query. I'm new to SQL in general and somewhat lost with this one. Does anybody have any suggestions as to how this should be handled?
One fairly simple approach starts with turning your case statement into a new table, listing form values versus what precedence value they should be sorted by:
id | form | precedence
-----------------------------------
1 | supervisor | 1
2 | installer | 2
(etc)
Create a model for this, say, FormPrecedences (not a great name, but I don't totally grok your data model so pick one that better describes it). Then, your query can look like this (note: I'm assuming your current model is called WorkItems):
when access_level > 2
joins("LEFT JOIN form_precedences ON form_precedences.form = work_items.form")
.where("completed = 'f'")
.order("COALESCE(form_precedences.precedence, 6)")
.limit(1)
The way this works isn't as complicated as it looks. A "left join" in SQL simply takes all the rows of the table on the left (in this case, work_items) and, for each row, finds all the matching rows from the table on the right (form_precedences, where "matching" is defined by the bit after the "ON" keyword: form_precedences.form = work_items.form), and emits one combined row. If no match is found, a LEFT JOIN will still emit a row, but with all the right-hand values being NULL. A normal join would skip any rows with no right-hand match found.
Anyway, with the precedence data joined on to our work items, we can just sort by the precedence value. But, in case no match was found during the join above, that value will be NULL -- so, I use COALESCE (which returns the first of its arguments that's not NULL) to default to a precedence of 6.
Hope that helps!

Sybase compare columns with duplicate row ids

So far I have a query with a result set (in a temp table) with several columns but I am only concerned with four. One is a customer ID(varchar), one is Date (smalldatetime), one is Amount(money) and the last is Type(char). I have multiple rows with the same custmer ID and want to evaluate them based on Date, Amount and Type. For example:
Customer ID Date Amount Type
A 1-1-10 200 blue
A 1-1-10 400 green
A 1-2-10 400 green
B 1-11-10 100 blue
B 1-11-10 100 red
For all occurrences of A I want to compare them to identify only one, first by earliest date, then by greatest Amount, then if still tied by comparing Types. I would then return one row for each customer.
I would provide some of the query but I am at home now after spending two days trying to get a correct result. It looks something like this:
(query to populate #tempTable)
GROUP BY customer_id
HAVING date_cd =
(SELECT MIN(date_cd)
FROM order_table ot
WHERE ot.customerID = #tempTable.customerID
)
OR date_cd IS NULL
I assume the HAVING would result in only one row per customer_id. This did not end up being the case since there were some ties there.
I am not sure I can do the OR - there are some with NULL values here - and it did not account for the step to the next comparison if they were all the same anyway. I am not seeing a way to avoid doing some row processing of the temp table with some kind of IF or WHERE loop.
As I write I am thinking maybe I use #tempTable.date_cd in the HAVING clause instead of looking at the original table. but that should return the same dates?
Am I on the right track or is there something missing? Suggestions? More info??
try below query :-
select * from #tempTable
GROUP BY customer_id
HAVING isnull(date_cd,"1900/01/01") =min(isnull(date_cd,"1900/01/01"))

Resources