Neo4j - How to build proper multi-dimensional query for my graph - graph

I have a simple social-networking like graph w/ users, friends, comments, likes etc. Users can "own" items, comment on "items", like "items". I am trying to write a cypher query that returns "items" along w/ extra information to display them in my stream.
I have tried using optional match and collect and stuff, but there is always some part of the result that doesn't work.
Specifically, for a given user(say user1), I want to return "items" that:
a specific user + his friends own
show number of likes,
also show number of comments,
Know if the item is already owned by me (so I can hide "own" button in the UI)
If the item is owned by friends, I want to show name, image of up to 2 friends (but not more than 2 friends if, say, 5 friends own that item)
You can copy-paste below to get the graph
// 3 users
CREATE (u1:USER{name:"USER1", image: "image1"})
CREATE (u2:USER{name:"USER2", image: "image2"})
CREATE (u3:USER{name:"USER3", image: "image3"})
//3 items
CREATE (i1:ITEM{name:"ITEM1"})
CREATE (i2:ITEM{name:"ITEM2"})
CREATE (i3:ITEM{name:"ITEM3"})
// OWNERSHIP ..
//user1 owns 2 items
CREATE (u1)-[:OWNS]->(i1)
CREATE (u1)-[:OWNS]->(i2)
// user2 owns i2 and i3
CREATE (u2)-[:OWNS]->(i2)
CREATE (u2)-[:OWNS]->(i3)
// user3 also owns i2 and i3 (so i2 is owned by u1, u2 and u3; and i3 is owned by u2 and u3)
CREATE (u3)-[:OWNS]->(i2)
CREATE (u3)-[:OWNS]->(i3)
// FRIENDSHIP..
// user1 is friend of both user2 and user3
CREATE (u1)-[:FRIEND_OF]->(u2)
CREATE (u1)-[:FRIEND_OF]->(u3)
// COMMENTS ..
//user1 has commented on all those items he owns
CREATE (u1i1:COMMENT{text:"user1 comment on item1"})
CREATE (u1)-[:COMMENTED]->(u1i1)-[:COMMENT_FOR]->(i1)
CREATE (u1i2:COMMENT{text:"user1 comment on item2"})
CREATE (u1)-[:COMMENTED]->(u1i2)-[:COMMENT_FOR]->(i2)
//user 2 has also commented on all those he owns
CREATE (u2i2:COMMENT{text:"user2 comment on item2"})
CREATE (u2)-[:COMMENTED]->(u2i2)-[:COMMENT_FOR]->(i2)
CREATE (u2i3:COMMENT{text:"user2 comment on item3"})
CREATE (u2)-[:COMMENTED]->(u2i3)-[:COMMENT_FOR]->(i3)
// LIKES ..
//user1 has liked user2's and user3's items
CREATE (u1)-[:LIKED]->(i2)
CREATE (u1)-[:LIKED]->(i3)
//user2 has liked user1's items
CREATE (u2)-[:LIKED]->(i1)

Let's build your query up step by step:
Specifically, for a given user(say user1), I want to return "items" that:
a specific user + his friends own
MATCH (u:USER {name: "USER1"})-[:FRIEND_OF*0..1]-(friend:USER)-[:OWNS]-(i:ITEM)
WITH u,i,
// Know if the item is already owned by me (so I can hide "own" button in the UI)
sum(size((u)-[:OWNS]->(i))) > 0 as user_owns,
// If the item is owned by friends, I want to show name, image of up to 2 friends
collect({name:friend.name, image:friend.image})[0..2] as friends
RETURN u,i, user_owns, friends
// show number of likes,
sum(size(()-[:LIKED]->(i))) as like,
// also show number of comments,
sum(size(()-[:COMMENT_FOR]->(i))) as comments
Actually because it is such a good question, I sat down and created a GraphGist documenting each step here.

Fairly easy. First you need to have a variable path length match from 0..1 on FRIEND_OF returning either yourself. Follow to all items being owned by those.
Use OPTIONAL MATCH for likes and comments since there might or might not exist any.
Since there are potentially multiple paths to a single item, you need to count the distinct likes and comments.
To check if you already own the item, check the endpoint of the variable path match from above if its name is yours.
For getting up to two images of the friends owning the item filter the list for your friends and return the image property. Last step is to slice the collection for the first two elements using subscript operator.
MATCH (:USER { name:'USER1' })-[:FRIEND_OF*0..1]->(me_or_friend)-[:OWNS]->(item)
OPTIONAL MATCH (item)<-[l:LIKED]-()
OPTIONAL MATCH (item)<-[c:COMMENT_FOR]-()
WITH item, count(distinct l) AS likes, count(distinct c) AS comments,
collect(DISTINCT me_or_friend) AS me_or_friends
RETURN item, likes, comments,
ANY (x IN me_or_friends WHERE x.name='USER1') AS i_already_own,
[x IN me_or_friends WHERE x.name<>'USER1' | x.image][0..2] as friendImages
Final comment:
On SO we appreciate if you show in your question what you've already tried yourself to solve the problem. Question like "solve that problem for me" are not that much welcome.

Related

How can i avoid the duplicate Field counter from two user at the same time pressing the bottom

i have collection which contains the whole users products , and to avoid the casts of reading all docs to get the length of the collection , i increase one value +1 to every field once it's doc being created , ok now everything is ok
here i am using this method to chick if doc if exist or not so i can avoid the duplicate of counter in case offline mode
removeProduct1(){
FirebaseFirestore.instance.collection("product").doc('product1')
.get(const GetOptions(source: Source.server)).then((value1) {
if(value1.exists) {
FirebaseFirestore.instance.collection("product").doc('product1').delete();
FirebaseFirestore.instance.collection("product").doc('productCount').update({
'productCount': FieldValue.increment(-1),
});
}
}).onError((error, stackTrace) {
ScaffoldMessenger.of(context).showSnackBar(snackBar,);
});
}
also everything is ok , but i noticed if two user pressed the button for this method at the same time so its gonna be two value decrease -2
maybe someone will think its rarely when two user hit the button at the same time , well this is only simple sample of what i have for real instead , i have page which contains too many users which is not be rarely for being two user hit the button
EDIT FOR MOER EXPLATION
i have collection called Friends which contains User IDs of friends together
for example user1 his id is ABC and user2 his id is DEF
so id becomes final into the Friends collection like this ABCDEF by following function
and also this following function increase counter +1 for ever one of them
makeFriends(){
// here i make sure first if the friends Ids are not exists To avoid suspiciously increasing the counter in offline mode and its work very well
FirebaseFirestore.instance.collection("Friends").doc(user1Id+user2Id).get().then((value) {
if(!value.exists){
// here to add ids to the friends collection
FirebaseFirestore.instance.collection("Friends").doc(user1Id+user2Id).set({
'test':''
});
//here to increase counter in other colection which is users for both user
//user1
FirebaseFirestore.instance.collection("users").doc(user1Id).update({
'friendsCounter':FieldValue.increment(1)
});
//user2
FirebaseFirestore.instance.collection("users").doc(user2Id).update({
'friendsCounter':FieldValue.increment(1)
});
}
});
}
Okay now everything going fine and they became a friends with one value increased and their Ids became unit into Friends collection .. and here image for better explanation
image 1
image
here i have function responsible for deleting friend with decrease one value from FriendsCount Field for both of them
deleteFriends(){
// here i make sure first if the friends Ids are exists To
// avoid suspiciously decrease the counter in offline mode and its work very well
FirebaseFirestore.instance.collection("Friends").doc(user1Id+user2Id).get().then((value) {
if(value.exists){
// here to remove ids from the friends collection
FirebaseFirestore.instance.collection("Friends").doc(user1Id+user2Id).delete();
//here to decrease counter for both user
//user1
FirebaseFirestore.instance.collection("users").doc(user1Id).update({
'friendsCounter':FieldValue.increment(-1)
});
//user2
FirebaseFirestore.instance.collection("users").doc(user2Id).update({
'friendsCounter':FieldValue.increment(-1)
});
}
});
}
ok now also everything going fine .. lets say user1 deleted his friend user 2 , by the previous functionthe Friends id which is ABCDEFhas been deleted so next time if user 2 want to do the same opretion it will fail because Friends ids are not exist anymore ..
the problem IS :
lets say these two users are still friends and
these two user visited each others profiles and hit the button at the same time which is call the pervious function (deleteFriend) so they gonna get the same return value which is (yes exists) then will be double decrease value for both of them instead of one as required .
means if their friendsCount was 1 both of them when they was a friend so tee value will be -1 instead 0 because there two operation was proceed
and this happen only if these two user hit the button at the same moment , but if the user 1 hit the button before user 2 then user 2 want to do same operation so he ganna fail because friendId was deleted by user1 operation as a condition statement in previous function ..
if(value.exists) i thought it too fast to delete friendIds before it give same result to other user if they together call the function at the same time
of course i can prevent the button of calling the delete function in case one of these user is already deleted other but I didn't see this as a strong solution since i can't grantee traffic jam or any reason else like bad users behavior .. etc , also basically i can't do it because delete button must be available as long as they still a friends
Note: this also happen in addFriends buttom call too if two users hit it at the same time , double value +2 instead one ..
i need any way to avoid server to give same result if there was two user query same doc at the same time , or any good solution
If you need to both read and write a document to update it, you should use a transaction to prevent the race condition that you now have when multiple users perform the operation around the same time.
Here you definitely want to use a transaction, because if the delete fails for whatever reason, the decrement operation should also fail (and vice versa).
The FlutterFire documentation on transactions also has a good explanation and example code to get started.

Store a manipulated collection in Power Apps

(1) In my Canvas App I create a collection like this:
Collect(colShoppingBasket; {Category: varCategoryTitle ; Supplier: lblSupplier.Text ; ProductNumber: lblProductNumber.Text });;
It works - I get a collection. And whenever I push my "Add to shopping basket" button, an item are added to my collection.
(2) Now I want to sort the collection and then use the sorted output for other things.
This function sorts it by supplier. No problems here:
Sort(colShoppingBasket; Supplier)
(3) Then I want to display the SORTED version of the collection in various scenarios. And this is where the issue is. Because all I can do is manipulate a DISPLAY of the collection "colShoppingBasket" - (unsorted).
(4) What would be really nice would be the option to create and store a manipulated copy of the original collection. And the display that whereever I needed. Sort of:
Collect(colShoppingBasketSORTED; { Sort(colShoppingBasket; supplier) });; <--- p.s. I know this is not a working function
You could use the following:
ClearCollect(colShoppingBasketSorted, Sort(colShoppingBasket, Supplier))
Note that it is without the { }
This will Clear and Collect the whole colShoppingBasket sorted.
If you want to store the table in a single row in a collection, you can use
ClearCollect(colShoppingBasketSortedAlternative, {SingleRow: Sort(colShoppingBasket, Supplier)})
I wouldn't recommend this though because if you want to use the Sorted values you'd have to do something like this:
First(colShoppingBasketSortedAlternative).SingleRow -> this returns the first records of the colShoppingBasketSortedAlternative then gets the content of the SingleRow column, which in this case is a Collection
Note: You will need to replace the , with ; to work on your case

Order neo4j cypher query by node depth

I have the following graph:
I want to return all users which have the CAN_DISTRIBUTE Credits permission attached through a Role which APPLIES_ON a Group.
The following query returns both sara and admin as user names:
MATCH (users)-[:IS]->()<-[:CHILD_OF*0..]-(roles)-[:CAN_DISTRIBUTE]->(asset:Asset{name:"Credits"}),
(roles)-[:APPLIES_ON]->(group:Group{name:"Digital"})
WITH DISTINCT users
RETURN collect(users.name)
Now, I'm having a really hard time to order the users returned by their Role relationship depth. I want sara to be returned first as the Manager role is a child of SuperManager.
In english it's like saying, give me all the users which can distribute credits on group X, ordered by their role hierarchy.
Do you guys have any ideas?
Here is the query to create this graph:
CREATE (admin:User{name:"admin"})
CREATE (sara:User{name:"sara"})
CREATE (c:Asset{name:"Credits"})
CREATE (marketing:Group{name:"Marketing"})
CREATE (digital:Group{name:"Digital"})
CREATE (super_manager:Role{name:"SuperManager"})
CREATE (manager:Role{name:"Manager"})
CREATE (manager)-[:CAN_DISTRIBUTE]->(c)
CREATE (admin)-[:IS]->(super_manager)
CREATE (sara)-[:IS]->(manager)
CREATE (super_manager)-[:APPLIES_ON]->(marketing)
CREATE (super_manager)-[:APPLIES_ON]->(digital)
CREATE (manager)-[:APPLIES_ON]->(marketing)
CREATE (manager)-[:APPLIES_ON]->(digital)
CREATE (manager)-[:CHILD_OF]->(super_manager)
You can do it using the length of the entire path, this way:
MATCH p = (users)-[:IS]->()<-[:CHILD_OF*0..]-(roles)-[:CAN_DISTRIBUTE]->(asset:Asset{name:"Credits"}),
(roles)-[:APPLIES_ON]->(group:Group{name:"Digital"})
WITH DISTINCT users, length(p) as pathLength
RETURN users.name
ORDER BY pathLength
The output for the given data set:
╒════════════╕
│"users.name"│
╞════════════╡
│"sara" │
├────────────┤
│"admin" │
└────────────┘

drupal rules user account unchanged

I'm using Drupal 7 + Rules 2 and would like to create a rule that shows the user a message when their role has changed from one role to another. I was experimenting with using "account-unchanged" in a data comparison condition, but wasn't having any luck. My configuration is below, however, using the "unchanged" feature is foreign to me, and I'm not sure I'm on the right track.
EVENT: After updating an existing user account
CONDITION:
Data Comparison - account-unchanged:roles equals RoleA
Data Comparison - account:roles equals RoleB
ACTION: Show a message on the site=Role changed!!!
As stated before, this produced no results when updating a user who had RoleA to having RoleB, and in the Rules debugger, the conditions are always evaluated to be FALSE.
If there is an alternate/better way of accomplishing this than the above, I would appreciate being switched to the right track :)
Thanks so much!
Was able to answer my own question!
EVENT: After updating an existing user account
CONDITION:
Data > List contains item - LIST: account-unchanged:roles, ITEM RoleA
Data > List contains item - LIST: account:roles, ITEM RoleB
ACTION: Show a message on the site=Role changed!!!
If you specifically want a message when a role was changed from one to the other, and not simply added, add another condition but NEGATE it. In my example, I would add:
Data > List contains item - LIST: account-unchanged:roles, ITEM RoleB (Check NEGATE)
Data > List contains item - LIST: account:roles, ITEM RoleA (Check NEGATE)
See https://groups.drupal.org/node/94379#comment-631148 for reference. I had been focusing on the VBO answer there and used this comment in conjunction with that approach and still had trouble. Just now thought to look at it on its own... and voila!
Hope this helps somebody!
In addition to arikiera's answer, if you would like to check if a user has had a role revoked you can use:
Data > List contains item - LIST: account-unchanged:roles, ITEM RoleA
Data > List contains item - LIST: account:roles, ITEM RoleB (Check
NEGATE)
I used this to add some rules to the subuser module which will remove a specific role from a parent user's sub users if the parent user has their role revoked. This is very useful if you are building a site that has corporate accounts who can add their own sub users.

Drupal OG Views: cannot create view of all groups of which I am *not* currently a member

(Also posted as http://drupal.org/node/596860)
I would like to create a view showing only those groups of which I am not already a member (or a pending member). However, the obvious way of doing this (take the og_my view and change the filter "Organic groups: Group member" to false) does not work.
The reason is that the SQL query essentially returns one row for every user in the group that matches your conditions. If you're searching for yourself (as in og_my), each group node will only show up once; if you're not searching for yourself, each group node shows up N times, where N is the number of other group members. Thus the groups of which I'm already a member continue to be displayed as long as there's at least one other member.
Does anyone have a way around this?
Thanks, Adrian
You may not be able to achieve this in Views directly as it does not support subqueries. My SQL came out like so:
SELECT node.nid AS nid, node.title AS node_title FROM node node LEFT JOIN og_uid og_uid ON node.nid = og_uid.nid WHERE (node.type IN ('campaign','setting','system')) AND node.nid NOT IN (select nid from og_uid where uid = 1);
It is said you can programmatically forward the results of such a query into the Views system for theming.
Maybe a special handling of the argument Organic Groups:Member of a Group, Exclude Argument option?
I also posted to d.o in hopes of pushing the issue conversation onward.
Wild. OG Views Extra was made available on d.o three days after your first revision of this question. I haven't used it, but looks just like it may do it.
So why install this module? If you need finer-grained control. The
available user options:
Not a Group Member
Group Member or Site Admin
Group Member Only

Resources