I am a beginner with Neo4j/Cypher and I'm having trouble modeling my data correctly.
The Data has following relationship:
MANUFACTURER_A (unique) -> PRODUCT_A (unique just withing MANUFACTURER_A) -> CUSTOMER_A (globally unique)
MANUFACTURER_A (unique) -> PRODUCT_A (unique just withing MANUFACTURER_A) -> CUSTOMER_B (globally unique)
MANUFACTURER_B (unique) -> PRODUCT_A (unique just withing MANUFACTURER_B) -> CUSTOMER_A (globally unique)
I am not able to make Product_A unique within a Manufacturer. I always get one line from Manufacturer_A to Product_A and another one from Manufacturer_B but I actually want two Product_A nodes (one from each Manufacturer), but just one Product_A node per Manufacturer.
I tried the following:
CREATE CONSTRAINT ON (c:MANUFACTURER) ASSERT c.MANUFACTURER IS UNIQUE;
CREATE CONSTRAINT ON (c:CUSTOMER) ASSERT c.CUSTOMER IS UNIQUE;
LOAD CSV WITH HEADERS FROM
'file:///small.csv' AS line
WITH line LIMIT 20
MERGE (CUSTOMER:Customer {Name: line.CUSTOMER})
MERGE (MANUFACTURER:Manufacturer {Name:line.MANUFACTURER})
MERGE (PRODUCT:Product {Name: line.PRODUCT})
MERGE (MANUFACTURER)<-[:PRODUCES]-(PRODUCT)
MERGE (CUSTOMER)<-[:CONSUMES]-(PRODUCT)
;
How would I model that correctly?
In this case, you don't want to MERGE the :PRODUCT node alone, but as part of a pattern connected to your merged manufacturer variable, that provides the context that the pattern you are looking for must be connected, and if no such pattern exists, the non-bound parts will be created.
...
MERGE (MANUFACTURER:Manufacturer {Name:line.MANUFACTURER})
MERGE (MANUFACTURER)<-[:PRODUCES]-(PRODUCT:Product {Name: line.PRODUCT})
...
So it won't matter if a product of that name exists elsewhere in the graph, as long as one isn't found connected to that manufacturer, it will be created as part of the MERGE.
This knowledge base article may be helpful as well, as it covers this application and more:
https://neo4j.com/developer/kb/understanding-how-merge-works/
Related
I have 2 Account Nodes, and several listing nodes, as seen below. The Match statement results in 2 Accounts being show with a relationship to each Listing thats associated to that account.
What im wanting to do is create a relationship between the two Accounts based on at least 1 of there listings each sharing the same phone number.
If possible im wanting to see the relationship between the two account nodes drawn and a relationship between the two listings so long as the listings are from different Account.
MERGE (:Account {account_id:11})
MERGE (:Listing {account_id:11, listing_id:1001, phone:99468320, author:'Paul'})
MERGE (:Account {account_id:12})
MERGE (:Listing {account_id:12, listing_id:1002, phone:97412521, author:'Sam'})
MERGE (:Listing {account_id:12, listing_id:1003, phone:97412521, author:'Sam'})
MERGE (:Listing {account_id:12, listing_id:1004, phone:99468320, author:'Sam'})
MERGE (:Listing {account_id:12, listing_id:1004, phone:0, author:'Same'})
MATCH (a:Account),(l:Listing)
WHERE a.account_id = l.account_id
CREATE (a)-[:LISTING]->(l)
RETURN a,l;
For the latter i did try the following but it went a bit crazy as it linked every listing to each other that had the same number appose to only doing so if the account_id was different.
match (p1:Listing)
with p1
match (p2:Listing)
where p2.phone = p1.phone and p1 <> p2
merge(p1)-[r:SHARED_PHONE]-(p2)
RETURN p1, p2
First of all, you should carefully consider if you really need the SHARED_PHONE relationships, as you will have to update the relationships every time a phone number is added, removed, or changed. That could complicate a lot of your queries and make your DB unnecessarily slower. Also, you could end up with a lot of SHARED_PHONE relationships (that you may not really need). Instead of creating the relationships, you could consider incorporating into the relevant queries the discovery of the nodes with the same phone numbers.
However, if you decide that you really need that relationship, here is one way to do what you want:
[UPDATED]
MATCH (n: Listing)
WITH n.phone AS phone, COLLECT(n) AS ns
FOREACH(i IN RANGE(0, SIZE(ns)-2) |
FOREACH(x IN [ns[i]] |
FOREACH(y IN [z IN ns[i+1..] WHERE x.account_id <> z.account_id] |
MERGE (x)-[:SHARED_PHONE]-(y)
)))
The WITH clause collects all the (unique) Listing nodes that share the same phone, and the nested FOREACH clauses execute the minimum number of MERGEs needed to ensure that all the appropriate nodes are connected by a SHARED_PHONE relationship (in either direction). The innermost FOREACH also ensures that the nodes to be connected do not have the same account_id.
I created my database in "SCHEMA-Editor" from structr.org. I have two nodes "Project" and "ProjectManager" and relationship between these is "MEMBER".
I made a cypher query to create the graph like this:
MATCH (a:Project),(b:ProjectManager)
WHERE a.name = 'X' AND b.name = 'Y'
CREATE (a)-[r:MEMBER]->(b)
RETURN r,a,b;
And i expected to display the graph, but i got this message: "MEMBER.id must_not_be_empty"
Nodes and relationships created with Cypher are not immediately usable in Structr, because Cypher bypasses the Structr layer and acts directly on the database level. There are some extra steps that need to be done to make an object known to Structr: it needs an id attribute that contains a random UUID (universally unique identifier).
See "Tools" -> "Admin" -> "Relationships" -> "Add UUIDs" in the Schema Editor.
Additional tip: in Cypher, you should use MERGE instead of CREATE to avoid creating duplicate relationships between the two nodes (depending on your use-case).
I have database with entities person (name,age) and project (name).
can I query the database in cypher that specifies me it is person or project?
for example consider I have these two instances for each :
Node (name = Alice, age= 20)
Node (name = Bob, age = 31)
Node (name = project1)
Node (name = project2)
-I want to know, is there any way that I just say project1 and it tells me that this is a project.
-or I query Alice and it says me this is a person?
Thanks
So your use case is to search things by name, and those things can be of several types instead of a single type.
Just to note, in general, this is not what Neo4j is built for. Typically in Neo4j queries you know the type of the thing you're searching for, and you're exploring relationships between that thing (or things) to figure out associations or data derived from that.
That said, there are ways to do this, though it's worth going through the rest of your use cases and seeing if Neo4j is really the best tool for what you're trying to do
Whenever you're querying by a property, you either want a unique constraint on the label/property, or an index on the label/property. Note that you need a combination of a label and a property for this; you cannot blindly ask for a node with a property without specifying a label and get good performance, as it will have to do a scan of all nodes in your database (there are some older manual indexes in Neo4j, but I'm not sure if these will continue to be supported; the schema indexes are recommended by the developers).
There is a workaround to this, as Neo4j allows multiple labels on the same node. If you only expect to query certain types by name (for example, only projects and people), you might create a :Named label, and set that label on all :Project and :Person nodes (and any other labels where it should apply). You can then create an index on :Named.name. That way your query would be something like:
MATCH (n:Named)
WHERE n.name = 'blah'
WITH LABELS(n) as types
WITH FILTER(type in types WHERE type <> 'Named') as labels
RETURN labels
Keep in mind that you haven't specified if a name should be unique among node types, so it could be possible for a :Person or a :Project or multiple :Persons to have the same name, unsure how that affects what should happen on your end. If every named thing ought to have a unique name, you should create a unique constraint on :Named.name (though again, it's on you to ensure that every node you create that ought to be :Named has the :Named label applied on creation).
You should use node labels (like Person and Project) to represent node "types".
For example, to create a person and a project:
CREATE (:Person {name: 'Alice', age: 20})
CREATE (:Project {name: 'project1'})
To find the project(s) named 'Fred':
MATCH (p:Project {name: 'Fred'})
RETURN p;
To get a collection of the labels of node n, you can invoke the LABELS(n) function. You can then look in that collection to see if the label you are looking for is in there. For example, if your Cypher query somehow obtains a node n, then this snippet would return n if and only if it has the Person label:
.
.
.
WHERE 'Person' IN LABELS(n)
RETURN n;
[UPDATED]
If you want to find all nodes with the name property value of "Fred":
MATCH (n {name: 'Fred'})
...
If you want to find all relationships with the name property value of "Fred":
MATCH ()-[r {name: 'Fred'})-()
...
If you want to match both in a single query, you have many ways to do that, depending on your exact use case. For example, if you want a cartesian product of the matching nodes and relationships:
OPTIONAL MATCH (n {name: 'Fred'})
OPTIONAL MATCH ()-[r {name: 'Fred'})-()
...
I am preplexed on why I am getting an issue with this Cypher statment when I have a unique constraint on the address of the location node but am using a merge which should find that if it exists and only return the id for the rest of the statment. What am I missing?
Here is my statement:
MERGE(l:Location{location_name:"Starbucks", address:"36350 Van Dyke Ave", city: "Sterling Heights",state: "MI", zip_code:"48312",type:"location",room_number:"",long:-83.028889,lat:42.561152})
CREATE(m:Meetup{meet_date:1455984000,access:"Private",status:"Active",type:"project",did_happen:"",topic:"New features for StudyUup",agenda:"This is a brainstorming session to come with with new ideas for the companion website, StudyUup. Using MatchUup as the base, what should be added, removed, or modified? Bring your thinking caps and ideas!"})
WITH m,l
MATCH (g:Project{title_slug:"studyuup"}) MATCH (p:Person{username:"wkolcz"})
WITH m,l,g,p
MERGE (g)-[:CREATED {rating:0}]->(m)
MERGE (m)-[:MEETUP_AT {rating:0}]->(l)-[:HOSTED_MEETUP]->(m)
MERGE (m)<-[:ATTENDING]-(p)
RETURN id(m) as meeting_id
I am getting:
Node 416 already exists with label Location and property "address"=[36350 Van Dyke Ave]
You've encountered a common misunderstanding of MERGE. MERGE merges on everything you've specified within the single MERGE clause. So the order of operations are:
Search for a :Location node with all of the properties you've specified.
If found, return the node.
If not found, create the node.
Your problem occurs at step 3. Because a node with all of the properties you've specified does not exist, it goes to step 3 and tries to create a node with all of those properties. That's when your uniqueness constraint is violated.
The best practice is to merge on the property that you've constrained to be unique and then use SET to update the other properties. In your case:
MERGE (l:Location {address:"36350 Van Dyke Ave"})
SET l.location_name = "Starbucks",
l.city = "Sterling Heights"
...
The same logic is going to apply for the relationships you're merging later in the query. If the entire pattern doesn't exist, it's going to try to create the entire pattern. That's why you should stick to the best practice of:
MERGE (node1:Label1 {unique_property: "value"})
MERGE (node2:Label2 {unique_property: "value"})
MERGE (node1)-[:REL]-(node2)
I am not sure what I am doing wrong here, so here is how I create nodes
CREATE (urlnode_1:UrlNode {url:'url1', nodenumber:1})
CREATE (urlnode_2:UrlNode {url:'url2', nodenumber:2})
I create relations as follows
CREATE
(urlnode_1)-[:OutLink {anchor_text:['MY']}]->(urlnode_2)
Two nodes are created successfully first, now on running the code to create the relation, I would have liked the relation to exist between the two created nodes but it creates two new nodes say 3 and 4 and shows a relation between them. What am i doing wrong here?
To guide you the best way I can, let's sum up some Neo4j basics concerning node and relationships creation :
A node can have one or more labels, labels are meaned to group the nodes by domain (User, Speaker, Company, etc..see a label as a table name for e.g. ). A node can also have properties.
A relationship can have only ONE type, relationships are organizing the graph. Relationships can also have properties.
To create a node, you can use the CREATE writing clause :
CREATE (n:Person {firstname: 'John'})
The CREATE statement will not check if other nodes with same label and properties already exists, it will just create a new node
Relationships can also be created with the same clause :
MATCH (n:Person {firstname: 'John'}), (p:Person {firstname: 'Pierre'})
CREATE (n)-[:KNOWS]->(p)
A complete pattern can also be created in one go :
CREATE (n:Person {name:'Chris'})-[:KNOWS]->(p:Person {name:'Oliver'})
REMINDER : CREATE will not check for existing nodes.
--- AND NOW MERGE ---
MERGE will lazily check for existing nodes, see him as a MATCH OR CREATE clause :
MERGE (n:Person {firstname:'Fred'})
If the node with label Person and firstname Fred does not exist, the node will be created, otherwise nothing will happen. This is where come the handy ON MATCH and ON CREATE mentionned by #joslinm .
If you run this query multiple times after the node creation, your graph will not change, if you know the http protocol, you can say that MERGE is an indempotent request.
Be aware that, MERGE will ensure that an entire pattern exist in the database, by creating it if it does not already exist, meaning that if you do MERGE with a complete pattern, the entire pattern will be looked up for existence, not a single node :
Say a node with label Person and name property with value 'John' already exist in the db :
MERGE (n:Person {name:'John'})
will not affect the graph
However :
MERGE (n:Person {name:'John'})-[:KNOWS]->(:Person {name:'Nathalia'})
A new John node will be created, because the entire pattern does not exist.
It is recommended to use MERGE incrementally :
MERGE (n:Person {name:'John'})
MERGE (p:Person {name:'Nathalia'})
MERGE (n)-[:KNOWS]->(p)
If you want to know more about the MERGE clause, I can highly recommend you this wonderful article from Luanne on GraphAware : http://graphaware.com/neo4j/2014/07/31/cypher-merge-explained.html
Chris
If you create a relationship, a new one will get created every single time. They are not inherently unique. It sounds like you'd rather be merging the relationship; i.e., if they relationship is there, match it, if not, create it.
The merge syntax for it is as follows:
MERGE (a:Node)-[:LIKES]->(b:Node)
ON
MATCH SET a.msg = 'I matched!'
ON
CREATE SET a.msg = 'I created!'
RETURN a
You can try it out here: http://console.neo4j.org/
You'll notice that first the msg will be "I created!" then after it matches, it will be "I matched!"