How can we copy labels from one node to another in one cypher? - neo4j

The question is just as the title, and the cypher statement is just like the follow:
Match (n: test) CREATE (copy : LABELS(n)) set copy = n
Its purpose is to create a node with the same properties and same labels as the other node, but it doesn't work now because we cannot use a expression like LABELS(n) to set lobel to a node.
How can I make it work?

Unfortunately, labels currently cannot be set directly from data values.

You could get the node's properties and labels you want to copy and then dynamically create another cypher statement that you execute.
Using the transactional api, it could look like this:
// requires cypher-rest
// npm i cypher-rest
'use strict';
const util = require('util');
const c = require('cypher-rest');
const neoUrl = 'http://127.0.0.1:7474/db/data/transaction/commit';
const copyNode = propertyObjMatch => {
return new Promise((resolve, reject) => {
// find node(s) via property matching and return it(/them)
const cypher = `MATCH (x ${util.inspect(propertyObjMatch)}) RETURN DISTINCT x, LABELS(x) AS labels`;
return c.run(cypher, neoUrl, true) // third parameter set to true to always return a list of results
.then(results => {
// iterate over results and create a copy one by one
results.forEach(result => {
const copy = `CREATE (copy:${[...result.labels].join(':')}) SET copy = ${util.inspect(result.x)} RETURN copy`;
c.run(copy, neoUrl);
});
})
});
};
// create a node
c.run('CREATE (x:LABEL1:LABEL2 {withProp: "and value", anotherProp: "test"}) RETURN x', neoUrl).then(() => {
copyNode({withProp: 'and value', anotherProp: 'test'})
.then(console.log)
});
Please excuse the hackiness, but it should bring the point across.

Related

neo4j - cypher query to find nodes which properties match object parameter

I'm looking for a query which can find all of the nodes which have properties that match a parameter which is an object. The properties of the node can be a superset (contain more properties than the filter)
e.g.
:param obj : { first : "first" }
CREATE (n:Test { first : "first", second : "second" });
CREATE (m:Test { first : "first" });
CREATE (f:Fail { first : "bad", second : "second" });
MATCH (c)
WHERE
PROPERTIES(c) = $obj
RETURN c;
n and m should be returned as they are matching on first : "first"
it is doable with apoc, basically by matching the obj with a submap of the properties, containing only the keys that are also present in obj
WITH { first : "first" } AS obj
MATCH (c)
WHERE apoc.map.submap(properties(c),keys(obj),[],false)= obj
RETURN c

How to add unique data to a neo4j graph database

I am adding iteratively data to my neo4j database but I am stuck with how to overwrite or update existing data and to check whether the data does not already exist in there.
Basically I have a set of movies with their corresponding id's, e.g.:
[
{id: 'gameofthrones', genre: 'fantasy', release: '2017'},
{id: 'inception', genre: 'scifi', release: '2010'},
...
]
I can add the movies as follows:
CREATE
(m1:Movie {id: 'gameofthrones', genre: 'fantasy', release: '2017'}),
(m2:Movie {id: 'inception', genre: 'scifi', release: '2010'})
However, when I run the script two times, then it creates 4 nodes instead of keeping it at two nodes.
So my question is, how can I make sure that it checks whether the node id is already present, and if so overwrite it instead of creating a new node?
I tried (but only the properties get added)
// data
attributes['id'] = 'gameofthrones';
attributes['genre'] = 'fantasy';
...
// query
MERGE ( s:Movie {attributes}.id)
ON CREATE SET ( s:Movie {attributes} )
which I call in NodeJS as follows:
executeQuery(queryStr, {"attributes": attributes})
// cypher (nodejs)
function executeQuery(queryStr, params) {
var qq = Q.defer();
db.cypher({
query: queryStr,
params: params,
}, function (error, results) {
if (error) {
qq.reject(error);
} else {
if (results.length != 0) {
qq.resolve(true);
} else {
qq.resolve(false);
}
};
});
return qq.promise;
};
you must change your query to this
MERGE ( s:Movie {attributes}.id)
ON CREATE SET s += {attributes}
ON MATCH SET s += {attributes} // optional
this should work, but you should use apoc.map.clean() so you do not set the id twice, which can cause some problems.
MERGE ( s:Movie {attributes}.id)
ON CREATE SET s += apoc.map.clean({attributes},['id'],[])
You can achieve this with MERGE clause as follows
MERGE (m1:Movie {id: 'gameofthrones'})
ON CREATE SET m1.genre = 'fantasy', m1.release = '2017'
MERGE (m2:Movie {id: 'inception'})
ON CREATE SET m2.genre: 'scifi', m2.release = '2010'
Ideally you want to create queries with parameters instead of literal strings. You can achieve this if you user apoc.load.json
with "file:///home/sample.json" as url // can be also http://url/sample.json
CALL apoc.load.json(url) yield value
UNWIND value as item
MERGE (m1:Movie {id: item.id})
ON CREATE SET m1.genre = item.genre, m1.release = item.release
example for dynamic properties with apoc functions:
with "file:///home/sample.json" as url // can be also http://url/sample.json
CALL apoc.load.json(url) yield value
UNWIND value as item
MERGE (m1:Movie {id: item.id})
ON CREATE SET m1 += apoc.map.clean(item,['id'],[])
or if you do not have apoc plugin:
with "file:///home/sample.json" as url // can be also http://url/sample.json
CALL apoc.load.json(url) yield value
UNWIND value as item
MERGE (m1:Movie {id: item.id})
ON CREATE SET m1 += item
note that id will first be merged and later updated with ON CREATE SET and you want to avoid writing a single property twice, using apoc and above query we can achieve that

Neo4j cypher query: Exclude subpaths from MATCH

I would like to match certain paths in my graph. These good paths should not contain certain subpaths, e.g. avoiding certain nodes.
For example, given the graph
a->b->c->d
a->avoid1->b
c->avoid2->d
NB: There could be many more nodes in between the edges I specified, e.g. a->t0->t1->b or a->avoid1->t2->b.
Now I would like to get all paths from a to d which do not contain certain subpaths, to be precise, those subpaths going from a over avoid1 to b and from c over avoid2 to d.
My current (insufficient) approach is to MATCH the entire path I am looking for and then specifying the node I want to avoid:
MATCH p=(a)-[:CF*]->(b)-[:CF*]->(c)-[:CF*]->(d)
WHERE NOT (avoid1 IN nodes(p))
This is not working out for me because I actually need to "filter out" subpaths and not nodes.
I need something like this:
MATCH p=(a)-[:CF*]->(b)-[:CF*]->(c)-[:CF*]->(d)
WHERE NOT ( (a)-[:CF*]->(avoid1)->[:CF*]->(b) IN p) AND NOT ( (c)-[:CF*]->(avoid2)->[:CF*]->(d) )
This does not work, I know but it could help to explain what I need: a way to filter out paths based on the fact if they contain certain subpaths.
EDIT:
Here are the commands:
MERGE (a:MYTYPE { label:'a' })
MERGE (b:MYTYPE { label:'b' })
MERGE (c:MYTYPE { label:'c' })
MERGE (d:MYTYPE { label:'d' })
MERGE (avoid1:MYTYPE { label:'avoid1' })
MERGE (avoid2:MYTYPE { label:'avoid2' })
CREATE (a)-[:CF]->(b)
CREATE (b)-[:CF]->(c)
CREATE (c)-[:CF]->(d)
CREATE (a)-[:CF]->(avoid1)
CREATE (avoid1)-[:CF]->(b)
CREATE (c)-[:CF]->(avoid2)
CREATE (avoid2)-[:CF]->(d)
and my current try (as suggested by dave's answer):
MATCH (a:MYTYPE { label:'a' })
MATCH (b:MYTYPE { label:'b' })
MATCH (c:MYTYPE { label:'c' })
MATCH (d:MYTYPE { label:'d' })
MATCH (avoid1:MYTYPE { label:'avoid1' })
MATCH (avoid2:MYTYPE { label:'avoid2' })
MATCH p=(a)-[:CF*]->(b)-[:CF*]->(c)-[:CF*]->(d)
WHERE NOT ( (a)-[:CF*]->(avoid1 {label:'avoid1'})-[:CF*]->(b) )
RETURN p
Yet, this gives me "(no rows)".
This query should allow you to filter on paths:
MATCH p=(a)-[:CF*]->(b)-[:CF*]->(c)-[:CF*]->(d)
WHERE NOT ( (a)-[:CF*]->()-[:CF*]->(b))
AND NOT ( (c)-[:CF*]->()-[:CF*]->(d) )
return p;`
You could also specify a label/property for the node that you want to filter on:
MATCH p=(a)-[:CF*]->(b)-[:CF*]->(c)-[:CF*]->(d)
WHERE NOT ( (a)-[:CF*]->(:Person {name:'Dave'})-[:CF*]->(b)) AND NOT ( (c)-[:CF*]->()-[:CF*]->(d) )
return p;

How to search a node filtering by an array field using multiple values

I store some values inside an array of a node and I would like to filter it using more than a single value. How could I do that?
Here it is my node:
CREATE (t:Test { value:["a","b","c"] } );
CREATE (t:Test { value:["a","b","d"] } );
CREATE (t:Test { value:["a","b","x"] } );
CREATE (t:Test { value:["a","y","z"] } );
CREATE (t:Test { value:["a","f","k"] } );
I would like to create a query that retrieves only items that "a" and "b" are inside values column.
The query below is not working but is close to what I would like to do.
MATCH (c:Test) WHERE ["a", "b"] IN c.value RETURN c
I know that I could split it using two "IN" commands (example below) but I would to like to avoid creating dynamic queries on my system.
MATCH (c:Test)
WHERE ALL(x IN ["a","b"] WHERE x IN c.value)
RETURN c.value

Creating relationships taking too long in Neo4j using Neo4php

I have been trying to create nodes and relations ships for our new module with neo4jphp [https://github.com/jadell/neo4jphp/wiki].
I am using cypher queries for the same.
Creating nodes with below query:
$queryNodes = "CREATE (n:User { props } ) ";
$query = new Everyman\Neo4j\Cypher\Query($client, $queryNodes, array('props' => $arrNodeProperties));
$result = $query->getResultSet();
Creating relationships with below query:
$queryRelations = "
MATCH (authUser: User { userid: 0001 }),(friend)
WHERE friend.userid IN ['" . implode("','",$relations) . "']
CREATE UNIQUE (authUser)-[r:KNOWS { connection: 'user_friend' }]->(friend)";
So far node creation works gr8.
But when i try to create Unique relationships for the nodes, it takes too long....
Note:
There is unique constraint userid for label User, hence node with label user is indexed by Neo4j on property userid.
CREATE CONSTRAINT ON (user:User) ASSERT user.userid IS UNIQUE
Questions:
Is there any other way we can achieve creating unique relationships.
Can i use index on relationships?? If Yes how can I achieve the same.
You might try use use MERGE instead of CREATE UNIQUE. Additionally use a Cypher parameter for the fried's list instead of concatenation on client side, see http://docs.neo4j.org/chunked/stable/cypher-parameters.html
Finally I worked it out with few changes...
Thanks #MichaelHunger for the help.
So here is how i did it...
Creating Unique Nodes using MERGE, FOREACH, ON CREATE SET and params:
$queryNodes = "
FOREACH (nodeData IN {nodeProperties}|
MERGE (n:User { userid: nodeData.userid })
ON CREATE SET
n.login = nodeData.login,
n.userid = nodeData.userid,
n.username = nodeData.username,
n.name = nodeData.name,
n.gender = nodeData.gender,
n.profile_pic = nodeData.profile_pic,
n.create_date = timestamp()
ON MATCH SET
n.update_date = timestamp()
)
";
$query = new Everyman\Neo4j\Cypher\Query($client, $queryNodes, array('nodeProperties' => $arrNodeProperties));
$result = $query->getResultSet();
Creating Unique Relationships with below query:
$queryRelations = "
MATCH (authUser: User { userid: {authUserid} }), (friend:User)
WHERE friend.userid IN {friendUserIds}
CREATE UNIQUE (authUser)-[r:KNOWS { connection: 'user_friend' }]->(friend)
";
$query = new Everyman\Neo4j\Cypher\Query($client, $queryRelations, array('friendUserIds' => $arrFriendUserId, 'authUserid' => $authUserid));
$result = $query->getResultSet();
Please comment if we can improve the same even further.

Resources