in my graph we have users that have 2 relation called "favorite" and "seen" with products and product have relation called has with some specification as colors(red blue...) , types(jeans....) and sizes (30...)
so i make some query that when i wanna create favorite or seen relation , it makes a relation with that specific user and the specification of that product calling "weight" and set property for that called "score" and i wanna increase this score every time user that set product to favorite or just seeing that product for example when a user see the product score change to +10 and for favorite change to +20 and then we recommend products with specifications that have most score
my query is
match (user:Users{m_id:""}),(m:Products{m_id:""})-[:HAS]->(a:Specifications)
MERGE (user) -[:FAVORITE]-> (m)
merge (user)-[:WEIGHT{score:0}]->(a)
and one more problem with this query is i dont wanna make new relation if i already have it i just wanna increase the scoreenter image description here
You would only merge on the relationship and then set the property accordingly.
MERGE (user)-[w:WEIGHT]-(a)
ON CREATE SET w.score = 10
ON MATCH SET w.score = w.score+10
Or alternatively update it unconditionally
MERGE (user)-[w:WEIGHT]-(a)
SET w.score = coalesce(w.score,0)+10
Related
I have a Neo4j db with different labels on the nodes such as a:Banker , b:Customer. each has an email property I want to search for an email but but not search the entire db. So I want to do something like this Match(a:Banker {email: '123#mymail.com'}) OR Match (b:Customer {email:'123#mymail.com'}). There are constraints on email for both labels but I don't want each label to have the same email so before I add a node I need to determine if the email exist in either Banker or Customer nodes. I suspect this can be done in a very efficient scalable way that would not leave the user staring at a spinner when trying to add the one millionth record.....any help would be much appreciated
How I would do it is have an addition label 'Person' on all Bankers and Customers.
CREATE CONSTRAINT ON (b:Person) ASSERT p.Email IS UNIQUE
CREATE CONSTRAINT ON (b:Banker) ASSERT p.Email IS UNIQUE
CREATE CONSTRAINT ON (b:Customer) ASSERT p.Email IS UNIQUE
CREATE (b:Person:Banker {Email: "123#mymail.com"})
CREATE (b:Person:Customer {Email: "321#mymail.com"})
CREATE (c:Person:Customer {Email: "123#mymail.com"})
The last one will fail as a Person/Banker already has the same email. You can then also search MATCH (p:Person {Email: "123#mymail.com"}) or even b:Banker, c:Customer
You can also do (p:Person:Customer:Banker) if a person is all three.
It will also allow you to do MERGE which creates an entry if it doesn't already exist.
Since you already have a database you can do:
MATCH(b:Banker)
SET b:Person
MATCH(c:Customer)
SET c:Person
A somewhat "safer" approach than #Liam's would be to just have the Person label, without the Banker and Customer labels. That way, it would be harder to accidentally create/merge a node without the Person label, since that would be the only label for a person. Also, this approach would not require 2 (or 3) uniqueness checks every time you added a person.
With this approach, you could also add isCustomer and isBanker boolean properties, as needed, and create indexes on :Person(isCustomer) and :Person(isBanker) to quickly locate customers versus bankers.
Now, having said the above, I wonder if you really need the isCustomer and isBanker properties (or the Customer and Banker labels) at all. That is, the fact that a Person node is a banker and/or a customer may be derivable from that node's relationships. It seems reasonable for your data model to contain Bank nodes with relationships between them and people. For example, in the following data model, b is a banker at "XYZ Bank", c is a customer, and bc is both:
(b:Person)-[:WORKS_AT]->(xyz:Bank {id:123, name: 'XYZ Bank'}),
(c:Person)-[:BANKS_AT]->(xyz),
(bc:Person)-[:BANKS_AT]->(xyz)<-[:WORKS_AT]-(bc)
This query would find all bankers:
MATCH (banker:Person)-[:WORKS_AT]->(:Bank)
RETURN banker;
This would find all customers:
MATCH (banker:Person)-[:BANKS_AT]->(:Bank)
RETURN banker;
This would find all bankers who are also customers at the same bank:
MATCH (both:Person)-[:WORKS_AT]->(:Bank)<-[:BANKS_AT]-(both)
RETURN both;
A shopping cart is being logged to Neo4j.
NOTE: Each cart is unique to a visitor and defined by the cart:path (which is actually a cart ID cookie). Each item is a lineitem in the cart. Its unique to the cart and product in the cart (unique key is item.key). Finally, product_id refers to a product.
The cart contains lines which need to be updated even when someone deletes a line or changes quantity.
The set updates values for existing lines, but it wouldn't remove lines that are deleted from the cart when the updated cart json arrives.
Is there a simple way to modify this query to delete removed lines automatically?
UNWIND items as item
MATCH (p:Cart {path:px.upath})
SET p.total_price=cart.total_price
MERGE (i:Item {key:item.key})
SET
i.product_id=item.product_id,
i.quantity=item.quantity,
MERGE (i)-[:LineOf]->(p)
I'm a little puzzled by the query, since it seems like you're merging in items as they're added to the cart, as opposed to matching on existing products. Also, the :Item nodes you're adding seem specific for a single user's transaction (you're setting the quantity based on the input), so it seems unwise to use MERGE here...what if two users are trying to add the same type of item at the same time, but different quantities? One of those transactions will overrule the other...user 1 adds 2 of :Item a, but user 2 adds 10 of item a, which changes user 1's :Cart to now read 10 of :Item a selected (though the :Cart's total hasn't been updated to that...) It seems like the model could use some improvements.
One way you could do this is to set the quantity on the relationship to the cart, instead of on the item.
Something like this:
UNWIND items as item
MATCH (p:Cart {path:px.upath})
SET p.total_price=cart.total_price
MERGE (i:Item {key:item.key})
// doesn't seem the place to set product_id, but leaving it in
SET i.product_id=item.product_id
MERGE (i)-[r:LineOf]->(p)
SET r.quantity=item.quantity
So it seems like this query is for adding items to the cart, and you need a query for deleting items.
This should do the trick:
// assume toDelete is your list of items to delete
WITH toDelete
MATCH (p:Cart {path:px.upath})
SET p.total_price=cart.total_price
MATCH (p)<-[r:LineOf]-(i)
WHERE i.key in toDelete
DELETE r
If you want a single query that will set your items as in your first query, and delete all other lines that are not sent over, then this combined query should work:
UNWIND items as item
MATCH (p:Cart {path:px.upath})
SET p.total_price=cart.total_price
// first delete all items from the cart that aren't listed
OPTIONAL MATCH (p)<-[r:LineOf]-(i)
WHERE NOT i.key in items
DELETE r
// now add/update the rest
MERGE (i:Item {key:item.key})
// doesn't seem the place to set product_id, but leaving it in
SET i.product_id=item.product_id
MERGE (i)-[r:LineOf]->(p)
SET r.quantity=item.quantity
I'm investigating the use of Neo4j to detect potentially fraudulent card transactions in near real time. I receive details of a customer and a card they've just used from our on-line systems. What I'm trying to do here is create new nodes for the customer and card if they don't exist, then establish the relationship between them.
Whenever the customer uses the card I want to set the time the card was last used, in addition, if this is the first time this customer-->card relationship has been seen, update totals of the number of cards the customer is associated with and the number of customers associated with the card.
The Cypher below seems to work, however I think it will re-evaluate the counts every time the relationship is seen, not just on the create. Is it possible to use the ON MATCH and ON CREATE in this statement to limit the unnecessary processing?
MERGE (c:customers {customer_id:"12345678"})
MERGE (a:cards {card_hash:"45uIic..."})
MERGE (c)-[r:has_card]->(a)
set r.last_transaction = "30-NOV-2016 07:58:42"
set a.card_ct = size(()-[:has_card]->(a))
set c.card_count = size((c)-[:has_card]->())
I'm running this from Python (using py2neo), I also want to return something back that will allow me to kick off a bespoke dijkstra based search of the neighborhood. Any ideas how I'd return some variable based on whether this was a new or existing relationship?
There is no need you to even have the card_ct or card_count properties.
Since neo4j 2.1, getting a count of the number of relationships of a specific type from a node is very efficient. So, every time you need a count, just use SIZE(()-[:has_card]->(node)) or SIZE((node)-[:has_card]->()).
How about something like this. Create a counter on a MATCH and if the counter is greater than zero then it is an existing relationship. Otherwise it is a new relationship.
MERGE (c:customers {customer_id:"12345678"})
MERGE (a:cards {card_hash:"45uIic..."})
MERGE (c)-[r:has_card]->(a)
ON MATCH SET r.num = coalesce(r.num, 0) + 1
set r.last_transaction = "30-NOV-2016 07:58:42"
set a.card_ct = size(()-[:has_card]->(a))
set c.card_count = size((c)-[:has_card]->())
RETURN
CASE
WHEN r.num > 0 THEN false
ELSE true
END as new_relationship
Here's the Cypher I've ended up with, thanks to Dave Bennett for his suggestion. I also realised that I don't need to initiate any further analysis if only 1 customer is associated with 1 card so I've excluded this as well.
MERGE (c:customers {customer_id:"12345678"})
MERGE (a:cards {card_hash:"BFgn..."})
MERGE (c)-[r:has_card]->(a)
ON CREATE SET a.card_scheme = "VISA DEBIT"
, a.card_ct = size(()-[:has_card]->(a))
, c.card_count = size((c)-[:has_card]->())
ON MATCH SET r.ind = 1
SET r.last_transaction = "06-Dec-2016 11:19:13"
RETURN CASE WHEN exists(r.ind)
AND a.card_ct + c.card_count > 2
THEN false
ELSE true END as new_relationship
My target is to create node + set new property to it in case not exists
if it's exist I just want to update it's property
Tried this:
MATCH (user:C9 {userId:'44'})
CREATE UNIQUE (user{timestamp:'1111'})
RETURN user
*in case the node with the property userId=44 already existed I just want to set it's property into 1111 else just create it and set it.
error I am getting:
user already declared (line 2, column 16 (offset: 46))
"CREATE UNIQUE (user{timestamp:'1111'})"
should I switch to Merge or?
thanks.
Yes, you should use the MERGE statement.
MERGE (user:C9 {userId:'44'})
// you can set some initial properties when the node is created if required
//ON CREATE SET user.propertykey = 'propertyvalue'
ON MATCH SET user.timestamp = '1111'
RETURN user
You mention unique constraints - I assume you have one set up. You definitely should do to prevent duplicate nodes being created. It will also create a schema index to improve the performance of your node lookup. If you do not yet have a unique constraint then it can be created like so
CREATE CONSTRAINT ON (u:C9) ASSERT u.userId IS UNIQUE
See the Neo4j MERGE documentation.
Finally, to understand what is going on in your query let's have a quick look line by line.
MATCH (user:C9 { userId:'44' })
This matches the node with label :C9 that has a userId property with value 44 and assigns it the identifier user.
CREATE UNIQUE (user{timestamp:'1111'})
This line is simply trying to create a new node with no label and a property timestamp with value '1111'. The exception you are seeing is a result of you using the same user identifier that has already been used in the first line. However, this is not a supported way to use CREATE UNIQUE as it requires a match first and will then create bits of the pattern that does not exist. The upside of this is that it is stopping this unwanted node (user{timestamp:'1111'}) being created in the graph.
RETURN user
This line is pretty self explanatory and is not being reached.
EDIT
There seems to be some confusion surrounding CREATE UNIQUE and when it should be used. This query
CREATE UNIQUE (user:C9 {timestamp:'1111'})
will fail with the message
This pattern is not supported for CREATE UNIQUE
To use CREATE UNIQUE you would first match an existing node and then use that to create a unique pattern in the graph. So to create a relationship from user to a second node you would use
MATCH (user:C9 { userId: '44' }
CREATE UNIQUE (user)-[r:FOO]-(bar)
RETURN r
If there is no relationships of type FOO from user then a new node will be created to represent bar and relationship of type :FOO will be created between them. Conversely, if the MATCH statement does not make a match then no nodes or relationships will be created.
I have nodes with Product Information (P). Every time a user likes the product, relationship [L] is created connecting user (U)->[L]->[P]
Now I need to retrieve a set of Product nodes based on specific condition, but also need to return additional information that whether they are liked by a specific user or not.
So if the Product Structure is like
{ Product Name, Price }
If lets say, 1 out of the 3 products in question is liked by user X, the result set I need may look something like this
[{Product1, 29.00, true}, {Product2, 39.00, false}, {Product3, 25.00, false}]
Here true refers to the fact that user has liked Product1 and has not liked Product2.
I am not sure how to write such a query that includes this additional information of whether the returned nodes are liked or not
I think something like this will suit your needs.
Match all the products. You will want to scope this match down in some way.
Optionally match user likes per product.
Return collection of maps that contain name, price and like status.
match (p:Product)
optional match (u:User)-[:LIKES]->p
with {product:p.name, price:p.price, like: case when u is null then false else true end} as Product_Detail
return collect(Product_Detail)