Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
Hello Michael, Thanks for this really great detailed answer ! Like it :) However I have a question : I do really prefer the last way (at the very bottom) using cypher and expressions to lazily limit results. I wonder whether it's also possible to order by car's name (not only limiting) the whole final collection. Indeed, as far as I know, order by could not be applied on Collect aggregate function directly. We would have to use With before collecting.. but the case here is that I end up with a collection incrementally built, so I'm forced to order a collection. Obviously, I can't incrementally build ordered collection since order should be applied on the whole directly. Is there a trick I may ignore to achieve global ordering ? Thanks a lot again :), Michael Le 1 août 2014 à 07:03, Michael Hunger michael.hun...@neotechnology.com a écrit : Not sure if I'd use cypher for those data volumes. I think in this case some imperative code filling a set of cars might be more sensible (i.e. a server extension) And using a label for the Car-Degree. (Alternatively you could also use a SELL1, SELL2 rel-type for the degrees, and then check if it has SELL(n..4) as sell relationships, that would probably be fastest. SetNode getCars(Node person, int level) { Label degree = DynamicLabel.label(Degree+level); for (Relationship knows = person.getRelationships(KNOWS)) { Node friend = knows.getOtherNode(person); for (Relationship sells : friend.getRelationships(SELLS,OUTGOING)) { Node car = sells.getEndNode(); if (car.hasLabel(degree)) { cars.add(car); if (cars.size() limit) return cars; } } } return cars; } Make sure to do real-sized load tests. Something I thought could work is incrementally building up the data. MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1) as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, cars + collect(c2) as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, cars + collect(c3) as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) WITH cars + collect(c4) as cars If you want to limit the cars, it would probably be more complicated. Something like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1)[0..{limit}] as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, case when length(cars) {limit} then cars + collect(c2)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, case when length(cars) {limit} then cars + collect(c3)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) RETURN case when length(cars) {limit} then cars + collect(c4)[0..({limit}-length(cars))] else cars end as cars it might even be more sensible to do the matches as expressions and only collect as few as you need. Like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) // iterate over all paths in the collection, extracting only the last node // but only taking the first 0..{limit} ones lazily from that collection WITH p1, [path in (p1)-[:SELLS]-(:Car) | last(path)][0..{limit}] ... On Fri, Aug 1, 2014 at 4:28 AM, Michael Azerhad michael.azer...@gmail.com wrote: Fix of my query above, I missed to specify the logged user node: MATCH (d:Degree {id: 1})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[KNOWS ]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 2})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[KNOWS *..2]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 3})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[KNOWS *..3]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 4})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[KNOWS *..4]-(loggedUser:Person{id: 123}) RETURN c It's important :) On Friday, August 1, 2014 4:24:41 AM UTC+2, Michael Azerhad wrote: Hi, I really think about making the following scenario optimal using Cypher and Neo4j 2.X.X: Let's suppose this classic person knowledge pattern: (a:Person)-[:KNOWS]-(b:Person) Each person can sell his car by specifying its visibility according to the degree of separation of its choice. Example: Person A wants to sell a car. He expects people distant of 2 degrees maximum to see his sale announcement. (friends of friends maximum, including his direct friends) Therefore, when Person B logs on and click on list all the sales, only sales that concerns him (according to the specified degree of separation by the seller previously). So if Person B is distant from Person
Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
You can sort on the client or use this at the end: unwind cars as car return car order by car.name asc Michael On Fri, Aug 1, 2014 at 9:41 AM, Michael Azerhad michael.azer...@gmail.com wrote: Hello Michael, Thanks for this really great detailed answer ! Like it :) However I have a question : I do really prefer the last way (at the very bottom) using cypher and expressions to lazily limit results. I wonder whether it's also possible to order by car's name (not only limiting) the whole final collection. Indeed, as far as I know, order by could not be applied on Collect aggregate function directly. We would have to use With before collecting.. but the case here is that I end up with a collection incrementally built, so I'm forced to order a collection. Obviously, I can't incrementally build ordered collection since order should be applied on the whole directly. Is there a trick I may ignore to achieve global ordering ? Thanks a lot again :), Michael Le 1 août 2014 à 07:03, Michael Hunger michael.hun...@neotechnology.com a écrit : Not sure if I'd use cypher for those data volumes. I think in this case some imperative code filling a set of cars might be more sensible (i.e. a server extension) And using a label for the Car-Degree. (Alternatively you could also use a SELL1, SELL2 rel-type for the degrees, and then check if it has SELL(n..4) as sell relationships, that would probably be fastest. SetNode getCars(Node person, int level) { Label degree = DynamicLabel.label(Degree+level); for (Relationship knows = person.getRelationships(KNOWS)) { Node friend = knows.getOtherNode(person); for (Relationship sells : friend.getRelationships(SELLS,OUTGOING)) { Node car = sells.getEndNode(); if (car.hasLabel(degree)) { cars.add(car); if (cars.size() limit) return cars; } } } return cars; } Make sure to do real-sized load tests. Something I thought could work is incrementally building up the data. MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1) as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, cars + collect(c2) as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, cars + collect(c3) as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) WITH cars + collect(c4) as cars If you want to limit the cars, it would probably be more complicated. Something like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1)[0..{limit}] as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, case when length(cars) {limit} then cars + collect(c2)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, case when length(cars) {limit} then cars + collect(c3)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) RETURN case when length(cars) {limit} then cars + collect(c4)[0..({limit}-length(cars))] else cars end as cars it might even be more sensible to do the matches as expressions and only collect as few as you need. Like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) // iterate over all paths in the collection, extracting only the last node // but only taking the first 0..{limit} ones lazily from that collection WITH p1, [path in (p1)-[:SELLS]-(:Car) | last(path)][0..{limit}] ... On Fri, Aug 1, 2014 at 4:28 AM, Michael Azerhad michael.azer...@gmail.com wrote: Fix of my query above, I missed to specify the logged user node: MATCH (d:Degree {id: 1})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 2})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS*..2]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 3})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS*..3]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 4})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS*..4]-(loggedUser:Person{id: 123}) RETURN c It's important :) On Friday, August 1, 2014 4:24:41 AM UTC+2, Michael Azerhad wrote: Hi, I really think about making the following scenario optimal using Cypher and Neo4j 2.X.X: Let's suppose this classic person knowledge pattern: (a:Person)-[:KNOWS]-(b:Person) Each person can sell his car by specifying its visibility according to the degree of separation of its choice. Example: Person A wants to sell a car. He expects people distant of 2 degrees maximum to see his sale
Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
wow! I wasn't aware of 'unwind' keyword, perfect ! And backported to 2.0.4 :) Thanks a lot Michael, really appreciate :) Michael Le 1 août 2014 à 10:56, Michael Hunger michael.hun...@neotechnology.com a écrit : You can sort on the client or use this at the end: unwind cars as car return car order by car.name asc Michael On Fri, Aug 1, 2014 at 9:41 AM, Michael Azerhad michael.azer...@gmail.com wrote: Hello Michael, Thanks for this really great detailed answer ! Like it :) However I have a question : I do really prefer the last way (at the very bottom) using cypher and expressions to lazily limit results. I wonder whether it's also possible to order by car's name (not only limiting) the whole final collection. Indeed, as far as I know, order by could not be applied on Collect aggregate function directly. We would have to use With before collecting.. but the case here is that I end up with a collection incrementally built, so I'm forced to order a collection. Obviously, I can't incrementally build ordered collection since order should be applied on the whole directly. Is there a trick I may ignore to achieve global ordering ? Thanks a lot again :), Michael Le 1 août 2014 à 07:03, Michael Hunger michael.hun...@neotechnology.com a écrit : Not sure if I'd use cypher for those data volumes. I think in this case some imperative code filling a set of cars might be more sensible (i.e. a server extension) And using a label for the Car-Degree. (Alternatively you could also use a SELL1, SELL2 rel-type for the degrees, and then check if it has SELL(n..4) as sell relationships, that would probably be fastest. SetNode getCars(Node person, int level) { Label degree = DynamicLabel.label(Degree+level); for (Relationship knows = person.getRelationships(KNOWS)) { Node friend = knows.getOtherNode(person); for (Relationship sells : friend.getRelationships(SELLS,OUTGOING)) { Node car = sells.getEndNode(); if (car.hasLabel(degree)) { cars.add(car); if (cars.size() limit) return cars; } } } return cars; } Make sure to do real-sized load tests. Something I thought could work is incrementally building up the data. MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1) as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, cars + collect(c2) as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, cars + collect(c3) as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) WITH cars + collect(c4) as cars If you want to limit the cars, it would probably be more complicated. Something like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1)[0..{limit}] as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, case when length(cars) {limit} then cars + collect(c2)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, case when length(cars) {limit} then cars + collect(c3)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) RETURN case when length(cars) {limit} then cars + collect(c4)[0..({limit}-length(cars))] else cars end as cars it might even be more sensible to do the matches as expressions and only collect as few as you need. Like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) // iterate over all paths in the collection, extracting only the last node // but only taking the first 0..{limit} ones lazily from that collection WITH p1, [path in (p1)-[:SELLS]-(:Car) | last(path)][0..{limit}] ... On Fri, Aug 1, 2014 at 4:28 AM, Michael Azerhad michael.azer...@gmail.com wrote: Fix of my query above, I missed to specify the logged user node: MATCH (d:Degree {id: 1})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 2})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS*..2]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 3})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS*..3]-(loggedUser:Person{id: 123}) RETURN c UNION MATCH (d:Degree {id: 4})-[:TARGET_TO]-(c:Car)-[:SELLS]-(p:Person)-[ KNOWS*..4]-(loggedUser:Person{id: 123}) RETURN c It's important :) On Friday, August 1, 2014 4:24:41 AM UTC+2, Michael Azerhad wrote: Hi, I really think about making the following scenario optimal using Cypher and Neo4j 2.X.X: Let's suppose this classic person knowledge pattern: (a:Person)-[:KNOWS]-(b:Person) Each person
Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
I tried one of the query you suggested: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1)[0..{limit}] as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, case when length(cars) {limit} then cars + collect(c2)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, case when length(cars) {limit} then cars + collect(c3)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) RETURN case when length(cars) {limit} then cars + collect(c4)[0..({limit}-length(cars))] else cars end as cars It takes more than 2 seconds to execute :s When I opened each node like I did before (evoked above), the query took 150 ms. I have only 51 cars in the graph and 15 persons ... Would the collect function impact the performance? Thanks :) Michael On Friday, August 1, 2014 10:56:04 AM UTC+2, Michael Hunger wrote: You can sort on the client or use this at the end: unwind cars as car return car order by car.name asc Michael On Fri, Aug 1, 2014 at 9:41 AM, Michael Azerhad michael...@gmail.com javascript: wrote: Hello Michael, Thanks for this really great detailed answer ! Like it :) However I have a question : I do really prefer the last way (at the very bottom) using cypher and expressions to lazily limit results. I wonder whether it's also possible to order by car's name (not only limiting) the whole final collection. Indeed, as far as I know, order by could not be applied on Collect aggregate function directly. We would have to use With before collecting.. but the case here is that I end up with a collection incrementally built, so I'm forced to order a collection. Obviously, I can't incrementally build ordered collection since order should be applied on the whole directly. Is there a trick I may ignore to achieve global ordering ? Thanks a lot again :), Michael Le 1 août 2014 à 07:03, Michael Hunger michael...@neotechnology.com javascript: a écrit : Not sure if I'd use cypher for those data volumes. I think in this case some imperative code filling a set of cars might be more sensible (i.e. a server extension) And using a label for the Car-Degree. (Alternatively you could also use a SELL1, SELL2 rel-type for the degrees, and then check if it has SELL(n..4) as sell relationships, that would probably be fastest. SetNode getCars(Node person, int level) { Label degree = DynamicLabel.label(Degree+level); for (Relationship knows = person.getRelationships(KNOWS)) { Node friend = knows.getOtherNode(person); for (Relationship sells : friend.getRelationships(SELLS,OUTGOING)) { Node car = sells.getEndNode(); if (car.hasLabel(degree)) { cars.add(car); if (cars.size() limit) return cars; } } } return cars; } Make sure to do real-sized load tests. Something I thought could work is incrementally building up the data. MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1) as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, cars + collect(c2) as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, cars + collect(c3) as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) WITH cars + collect(c4) as cars If you want to limit the cars, it would probably be more complicated. Something like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) OPTIONAL MATCH (p1)-[:SELLS]-(c1:Car) WITH p1, collect(c1)[0..{limit}] as cars OPTIONAL MATCH (p1)-[:KNOWS]-(p2:Person) OPTIONAL MATCH (p2)-[:SELLS]-(c2:Car:Degree2) WITH p2, case when length(cars) {limit} then cars + collect(c2)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p2)-[:KNOWS]-(p3:Person) OPTIONAL MATCH (p3)-[:SELLS]-(c3:Car:Degree3) WITH p3, case when length(cars) {limit} then cars + collect(c3)[0..({limit}-length(cars))] else cars end as cars OPTIONAL MATCH (p3)-[:KNOWS]-(p4:Person) OPTIONAL MATCH (p4)-[:SELLS]-(c4:Car:Degree4) RETURN case when length(cars) {limit} then cars + collect(c4)[0..({limit}-length(cars))] else cars end as cars it might even be more sensible to do the matches as expressions and only collect as few as you need. Like this: MATCH (loggedUser:Person{id: 123})-[:KNOWS]-(p1:Person) // iterate over all paths in the collection, extracting only the last node // but only taking the first 0..{limit} ones lazily from that collection WITH p1, [path in (p1)-[:SELLS]-(:Car) | last(path)][0..{limit}] ... On
Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
I guess I found the reason of the slowness: If Person A is friend with B, and B is friend with C and C is friend with A (cyclic), then the query returns duplicated cars. I end up with 180 cars instead of 51 .. -- You received this message because you are subscribed to the Google Groups Neo4j group. To unsubscribe from this group and stop receiving emails from it, send an email to neo4j+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
I had to add the keyword DISTINCT in my return clause like this: ... UNWIND cars as extractedCars RETURN DISTINCT(extractedCars) It took now about 150ms that is pretty fast. Is DISTINCT the best strategy in this case to avoid duplicates? Michael On Friday, August 1, 2014 2:26:19 PM UTC+2, Michael Azerhad wrote: I guess I found the reason of the slowness: If Person A is friend with B, and B is friend with C and C is friend with A (cyclic), then the query returns duplicated cars. I end up with 180 cars instead of 51 .. -- You received this message because you are subscribed to the Google Groups Neo4j group. To unsubscribe from this group and stop receiving emails from it, send an email to neo4j+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [Neo4j] Re: Returning items regarding the degree of separations between users.
Yep probably Sent from mobile device Am 01.08.2014 um 14:51 schrieb Michael Azerhad michael.azer...@gmail.com: I had to add the keyword DISTINCT in my return clause like this: ... UNWIND cars as extractedCars RETURN DISTINCT(extractedCars) It took now about 150ms that is pretty fast. Is DISTINCT the best strategy in this case to avoid duplicates? Michael On Friday, August 1, 2014 2:26:19 PM UTC+2, Michael Azerhad wrote: I guess I found the reason of the slowness: If Person A is friend with B, and B is friend with C and C is friend with A (cyclic), then the query returns duplicated cars. I end up with 180 cars instead of 51 .. -- You received this message because you are subscribed to the Google Groups Neo4j group. To unsubscribe from this group and stop receiving emails from it, send an email to neo4j+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout. -- You received this message because you are subscribed to the Google Groups Neo4j group. To unsubscribe from this group and stop receiving emails from it, send an email to neo4j+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.