apoc.load.jdbc check row property before create - neo4j

I'm using Apoc.load.jdbc to get data from Oracle Database and create row from it, here is the code:
call apoc.load.driver('oracle.jdbc.driver.OracleDriver')
WITH "jdbc:oracle:thin:#10.82.14.170:1521/ORACLE" as url
CALL apoc.load.jdbc(url,"select * from Patients",[],{credentials:{user:'KCB',password:'123'}}) YIELD row
Create (p:Person) set p=row
return p
That code work fine but I want to check row property before create it. Such as:
If (row.ID!=p.ID)
{
set p=row
}
Else{Not set}
How can I do that with my code? Thanks a lot!

As #TomažBratanič mentions in his answer, your desired conditional check makes no sense. That is, unless you also replace your CREATE clause.
Your query uses CREATE to always create a new p with no properties. So row.ID <> p.ID will always be true, and you'd always be executing the SET clause.
However, I believe your real intention is to avoid changing an existing Person node (and to avoid creating a duplicate Person node for the same person). So, below is a query that uses MERGE and ON CREATE to do that. I assume that people have unique ID values.
CALL apoc.load.driver('oracle.jdbc.driver.OracleDriver')
CALL apoc.load.jdbc(
"jdbc:oracle:thin:#10.82.14.170:1521/ORACLE",
"select * from Patients",[],{credentials:{user:'KCB',password:'123'}}
) YIELD row
MERGE (p:Person {ID: row.ID})
ON CREATE SET p = row
RETURN p
Also, you should consider creating an index (or uniqueness constraint) on :Person(ID) to optimize the lookup of existing Person nodes.

You can use a CASE statement to achieve this:
call apoc.load.driver('oracle.jdbc.driver.OracleDriver')
WITH "jdbc:oracle:thin:#10.82.14.170:1521/ORACLE" as url
CALL apoc.load.jdbc(url,"select * from Patients",[],{credentials:{user:'KCB',password:'123'}}) YIELD row
Create (p:Person) set p = CASE WHEN row.ID <> p.id THEN row ELSE null END
return p
However, this statement does not make sense, because you always create a new Person, so the row.ID will never be the same as p.id.

Related

Neo4j Cypher: Create a relationship only if the end node exists

Building on this similar question, I want the most performant way to handle this scenario.
MERGE (n1{id:<uuid>})
SET n1.topicID = <unique_name1>
IF (EXISTS((a:Topic{id:<unique_name1>})) | CREATE UNIQUE (n1)-[:HAS]->(a))
MERGE (n2{id:<uuid>})
SET n2.topicID = <unique_name2>
IF (EXISTS((a:Topic{id:<unique_name2>})) | CREATE UNIQUE (n2)-[:HAS]->(a))
Unfortunately, IF doesn't exist, and EXISTS can't be used to match or find a unique node.
I can't use OPTIONAL MATCH, because then CREATE UNIQUE will throw a null exception (as much as I wish it would ignore null parameters)
I can't use MATCH, because if the topic doesn't exist, I will will loose all my rows.
I can't use MERGE, because I don't want to create the node if it doesn't exist yet.
I can't use APOC, because I have no guarantee that it will be available for use on our Neo4j server.
The best solution I have right now is
MERGE (a:TEST{id:1})
WITH a
OPTIONAL MATCH (b:TEST{id:2})
// collect b so that there are no nulls, and rows aren't lost when no match
WITH a, collect(b) AS c
FOREACH(n IN c | CREATE UNIQUE (a)-[:HAS]->(n))
RETURN a
However, this seems kinda complicated and needs 2 WITHs for what is essentially CREATE UNIQUE RELATION if start and end node exist (and in the plan there is an eager). Is it possible to do any better? (Using Cypher 3.1)
You can simplify a quite a bit:
MERGE (a:TEST{id:1})
WITH a
MATCH (b:TEST{id:2})
CREATE UNIQUE (a)-[:HAS]->(b)
RETURN a;
The (single) WITH clause serves to split the query into 2 "sub-queries".
So, if the MATCH sub-query fails, it only aborts its own sub-query (and any subsequent ones) but does not roll back the previous successful MERGE sub-query.
Note, however, that whenever a final sub-query fails, the RETURN clause would return nothing. You will have to determine if this is acceptable.
Because the above RETURN clause would only return something if b exists, it might make more sense for it to return b, or the path. Here is an example of the latter (p will be assigned a value even if the path already existed):
MERGE (a:TEST{id:1})
WITH a
MATCH (b:TEST{id:2})
CREATE UNIQUE p=(a)-[:HAS]->(b)
RETURN p;
[UPDATE]
In neo4j 4.0+, CREATE UNIQUE is no longer supported, so MERGE needs to be used instead.
Also, if you want to return a even if b does not exist, you can use the APOC function apoc.do.when:
MERGE (a:TEST{id:1})
WITH a
OPTIONAL MATCH (b:TEST{id:2})
CALL apoc.do.when(
b IS NOT NULL,
'MERGE (a)-[:HAS]->(b)',
'',
{a: a, b: b}) YIELD value
RETURN a;

Find a graph node by a field, update all other fields

I have a Neo4J graph database where I want to store users and relationships between them.
I want to be able to update a User node that I find them by GUID with data contained in a .Net User object. Ideally I'd like to know how to do that in Neo4JClient but even plain Cypher query would do.
Ideally I'd like to use the whole object, not knowing what properties have been modified, and replace all of them - including array properties - unlike the example below that knows PhoneNumber is to be updated
Something like this:
MATCH (n:`User` {Id:'24d03ce7-8d23-4dc3-a13b-cffc0c7ce0d8'})
MERGE (n {PhoneNumber: '123-123-1234'})
RETURN n
The problem with the code above is that MERGE redefines the n
and I get this error:
n already declared (line 2, column 8) "MERGE (n {PhoneNumber: '123-123-1234'})" ^
If all you want to do is completely replace all the properties of existing nodes, do not use MERGE. You should just use MATCH, and SET all the properties. Something like this:
MATCH (n:`User` {Id:'24d03ce7-8d23-4dc3-a13b-cffc0c7ce0d8'})
SET n = {PhoneNumber: '123-123-1234', Age: 32}
RETURN n;
On the other hand, if you want to create a new node iff one with the specified Id does not yet exist, and you also want to completely replace all the properties of the new or existing node, you can do this:
MERGE (n:`User` {Id:'24d03ce7-8d23-4dc3-a13b-cffc0c7ce0d8'})
SET n = {PhoneNumber: '123-123-1234', Age: 32}
RETURN n;
Note: in the above queries, all the existing properties of n would be deleted before the new properties are added. Also, the map assigned to n in the SET clause can be passed to the query as a parameter (so no hardcoding is needed).

Neo4J: Renaming property keys

I just got started on Neo & tried to look for prior questions on this topic. I need help to rename one of the property keys.
I created the following node:
CREATE (Commerce:Category {title:' Commerce', Property:'Category', Owner:'Magic Pie', Manager:'Simple Simon'})
Now want to rename title to name. Is there a way to do it? I don't want to delete the node as there are 100's of nodes with the property "title".
Yes, you want to SET a new property name with the value of the old property title. And then REMOVE the old property title. Something like this...
MATCH (c:Category)
WHERE c.name IS NULL
SET c.name = c.title
REMOVE c.title
If you have MANY nodes, it is advisable to perform the operation in smaller batches. Here is an example of limiting the operation to 10k at a time.
MATCH (c:Category)
WHERE c.name IS NULL
WITH c
LIMIT 10000
SET c.name = c.title
REMOVE c.title
another solution would be using APOC functions:
MATCH (n) WHERE ID(n) = 1234
WITH *, collect(n) AS nodes // nodes must be converted into a collection first
CALL apoc.refactor.rename.nodeProperty("oldKey ", "newKey", nodes)
// rename doesn't work if the key has strings ^ postfixed in it
YIELD batches, total, timeTaken, committedOperations
RETURN *
in the case you accidentially added strings at the end (it's possible during create) the property cannot be accessed via:
SET n.newKey = n.oldKey
REMOVE n.oldKey
then you must use:
SET n.newKey = n.`oldKey `
REMOVE n.`oldKey `
this works

Can I create and relate two nodes with the same name but different ids in neo4j

I have created two nodes in neo4j with the same name and label but with different ids:
CREATE (P:names {id:"1"})
CREATE (P:names{id:"2"})
My question is if I can create a relationship between these two nodes like this:
MATCH (P:names),(P:names)
WHERE P.id = "1" AND P.id = "2"
CREATE (P)-[r:is_connected_with]->(P) RETURN r"
I try it but it doesn't work.
Is it that I shouldn't create nodes with the same name or there is a workaround?
How about the following?
First run the create statements:
CREATE (p1:Node {id:"1"}) // note, named p1 here
CREATE (p2:Node {id:"2"})
Then, do the matching:
MATCH (pFirst:Node {id:"1"}), (pSecond:Node {id:"2"}) // and here we can call it something else
CREATE pFirst-[r:is_connected_with]->(pSecond)
RETURN r
Basically, you are matching two nodes (with the label Node). In your match you call them p1 and p2 but you can change these identifiers if you wish. Then, simply create the relationship between them.
You should not create identifiers with the same name. Also note that p1 and p2 are not the name of the node, it is the name of the identifier in this particular query.
EDIT: After input from the OP I have created a small Gist that illustrates some basics regarding Cypher.
#wassgren has the right answer about how to fix your query but I might be able to fill in some details about why and it's too long to leave in a comment.
The character before the colon when describing a node or relationship is referred to as an identifier, it's just a variable representing a node/rel within a Cypher query. Neo4j has some naming conventions that you are not following and as a result, it makes your query harder to read and will be harder for you to get help in the future. Best practices are:
Identifiers start lowercase: person instead of Person1, p instead of P
Labels are singular and have their first character capitalized: (p1:Name), not (p1:Names) or (p1:names) or (p1:name)
Relationships are all caps, [r:IS_CONNECTED_WITH], not [r:is_connected_with], though this one gets broken all the time ;-)
Back to your query, it both won't work and it doesn't follow conventions.
Won't work:
MATCH (P:names),(P:names)
WHERE P.id = "1" AND P.id = "2"
CREATE (P)-[r:is_connected_with]->(P) RETURN r
Will work, looks so much better(!):
MATCH (p1:Name),(p2:Name)
WHERE p1.id = "1" AND p2.id = "2"
CREATE (p1)-[r:IS_CONNECTED_WITH]->(p2) RETURN r
The reason your query doesn't work, though, is that by writing MATCH (P:names),(P:names) WHERE P.id = "1" AND P.id = "2", you are essentially saying "find a node, call it 'P', with an ID of both 1 and 2." That's not what you want and it obviously won't work!
If you're trying to create many nodes, you would rerun this query for each pair of nodes you want to create, changing the ID you assign each time. You can create the nodes and their relationship in one query, too:
CREATE (p1:Name {id:"1"})-[r:IS_CONNECTED_WITH]->(p2:Name {id:"2"}) RETURN r
In the app, just change the ID you want to assign to the nodes before you run the query. The identifiers are instance variables, they disappear when the query is complete.
EDIT #1!
One more thing, setting the id property within your app and assigning it to the database instead of relying on the Neo4j-created internal ID is a best practice. I suggest avoiding sequential IDs and instead using something to create a unique ID. In Ruby, many people use SecureRandom::uuid for this, I'm sure there's a parallel in whatever language(s) you are using.
EDIT #2!
Neo4j supports integer properties. {id:"1"} != {id: 1}. If your field is supposed to be an integer, use an integer.

Neo4j merge return something only if it was created

Neo4j's merge will create new node if it doesn't exist. And it has ON CREATE and ON MATCH as two distinctions. However, is there a way to return different information if the node was created as to if the node was matched?
MERGE (charlie { name:'Charlie Sheen' })
ON CREATE SET charlie.name = 'Charlie'
RETURN charlie
Something like: ON CREATE RETURN 1, ON MERGE RETURN 0
There's a relevant example on the merge page of the documentation:
MERGE (keanu:Person { name:'Keanu Reeves' })
ON CREATE SET keanu.created = timestamp()
ON MATCH SET keanu.lastSeen = timestamp()
RETURN keanu.name, has(keanu.lastSeen);
Basically this stores your 0 or 1 flag in the existence or absence of the "lastSeen" attribute.
Your query has this oddity that you're matching on "Charlie Sheen" but then modifying the value you matched (name) to be "Charlie". That's odd -- it means that because you're modifying it each time, even if you add an ON MATCH clause, it will never fire. You'll create it new each time, then modify it, guaranteeing it will be created new the next time you run that query. I would guess that it's a bad idea to modify the terms of your match later in the query, unless it's a special circumstance.

Resources