diff --git a/docs/graph-refactor.adoc b/docs/graph-refactor.adoc new file mode 100644 index 0000000000..603b3fa3b3 --- /dev/null +++ b/docs/graph-refactor.adoc @@ -0,0 +1,287 @@ + +[cols="1m,5"] +|=== +| call apoc.refactor.cloneNodes([node1,node2,...]) | clone nodes with their labels and properties +| call apoc.refactor.cloneNodesWithRelationships([node1,node2,...]) | clone nodes with their labels, properties and relationships +| call apoc.refactor.mergeNodes([node1,node2]) | merge nodes onto first in list +| call apoc.refactor.to(rel, endNode) | redirect relationship to use new end-node +| call apoc.refactor.from(rel, startNode) | redirect relationship to use new start-node +| call apoc.refactor.invert(rel) | inverts relationship direction +| call apoc.refactor.setType(rel, 'NEW-TYPE') | change relationship-type +| call apoc.refactor.extractNode([rel1,rel2,...], [labels], 'OUT','IN') | extract node from relationships +| call apoc.refactor.collapseNode([node1,node2],'TYPE') | collapse node to relationship, node with one rel becomes self-relationship +| call apoc.refactor.normalizeAsBoolean(entity, propertyKey, true_values, false_values) | normalize/convert a property to be boolean +| call apoc.refactor.categorize(node, propertyKey, type, outgoing, label) | turn each unique propertyKey into a category node and connect to it +|=== + +TODO: + +* merge nodes by label + property +* merge relationships + +=== Graph Refactoring Examples + +.Clone nodes + +We create a dataset +[source,cypher] +---- +CREATE (f:Foo{name:'Foo'}),(b:Bar{name:'Bar'}) +---- + +As result we have two nodes + +image::{img}/apoc.refactor.cloneNodes.dataset.png[width=800] + +[source,cypher] +---- +MATCH (f:Foo{name:'Foo'}),(b:Bar{name:'Bar'}) WITH f,b +CALL apoc.refactor.cloneNodes([f,b]) yield input, output RETURN * +---- + +As result we have the two nodes that we have created before and their clones + +image::{img}/apoc.refactor.cloneNodes.png[width=800] + +.Clone nodes with relationship + +We create a dataset of two different nodes of type `Actor` connected with other two different node of type `Movie` + +[source,cypher] +---- +CREATE (k:Actor {name:'Keanu Reeves'})-[:ACTED_IN {role:'Neo'}]->(m:Movie {title:'The Matrix'}), + (t:Actor {name:'Tom Hanks'})-[:ACTED_IN {role:'Forrest'}]->(f:Movie {title:'Forrest Gump'}) RETURN * +---- + +image::{img}/apoc.refactor.cloneNodesWithRelationships.dataset.png[width=800] + +[source,cypher] +---- +MATCH (k:Actor {name:'Keanu Reeves'}), (t:Actor {name:'Tom Hanks'}) +CALL apoc.refactor.cloneNodesWithRelationships([k,t]) YIELD input, output RETURN * +---- + +As result we have a copy of the nodes and relationships + +image::{img}/apoc.refactor.cloneNodesWithRelationships.png[width=800] + +.Merge nodes + +We create two nodes with different properties + +[source,cypher] +---- +CREATE (f:Person {name:'Foo'}), (b:Person {surname:'Bar'}) RETURN f,b +---- + +image::{img}/apoc.refactor.mergeNodes.dataset.png[width=800] + +Now we want to merge these nodes into one + +[source,cypher] +---- +MATCH (f:Person {name:'Foo'}), (b:Person {surname:'Bar'}) +CALL apoc.refactor.mergeNodes([f,b]) +YIELD node RETURN node +---- + +image::{img}/apoc.refactor.mergeNodes.png[width=800] + +Thus we have one node with both properties `name` and `surname` + +.Redirect relationship to + +We start with two nodes related each other with a relationship. We create a new node which we will use to redirect the relationship like end node + +[source,cypher] +---- +CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) +CREATE (p:Person {name:'Antony'}) +RETURN * +---- + +image::{img}/apoc.refactor.to.dataset.png[width=800] + +[source,cypher] +---- +MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) with id(rel) as id +MATCH (p:Person {name:'Antony'}) with p as p +MATCH ()-[r]->(), (p:Person) CALL apoc.refactor.to(r, p) YIELD input, output RETURN * +---- + +image::{img}/apoc.refactor.to.png[width=800] + +Now the relationship is towards the new node `Person` + +.Redirect relationship from + +We start with two nodes related each other with a relationship. We create a new node which we will use to redirect the relationship like start node + +[source,cypher] +---- +CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) +CREATE (p:Person {name:'Antony'}) +RETURN * +---- + +image::{img}/apoc.refactor.from.dataset.png[width=800] + +[source,cypher] +---- +MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) with id(rel) as id +MATCH (p:Person {name:'Antony'}) with p as p +MATCH ()-[r]->(), (p:Person) CALL apoc.refactor.from(r, p) YIELD input, output RETURN * +---- + +image::{img}/apoc.refactor.from.png[width=800] + +Now the relationship starts from the new node `Person` from the old node `Bar` + +.Invert relationship + +We start with two nodes connected by a relationship + +[source,cypher] +---- +CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) +---- + +image::{img}/apoc.refactor.invert.dataset.png[width=800] + +Now we want to invert the relationship direction + +[source,cypher] +---- +MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) WITH id(rel) as id +MATCH ()-[r]->() WHERE id(r) = id +CALL apoc.refactor.invert(r) yield input, output RETURN * +---- + +image::{img}/apoc.refactor.invert.call.png[width=800] + +image::{img}/apoc.refactor.invert.png[width=800] + +.Set type + +With a simple relationship between two node + +[source,cypher] +---- +CREATE (f:Foo)-[rel:FOOBAR]->(b:Bar) +---- + +image::{img}/apoc.refactor.setType.dataset.png[width=800] + +We can change the relationship type from `FOOBAR` to `NEW-TYPE` + +[source,cypher] +---- +MATCH (f:Foo)-[rel:FOOBAR]->(b:Bar) with rel +CALL apoc.refactor.setType(rel, 'NEW-TYPE') YIELD input, output RETURN * +---- + +image::{img}/apoc.refactor.setType.png[width=800] + +.Extract node from relationships + +[source,cypher] +---- +CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) +---- + +image::{img}/apoc.refactor.extractNode.dataset.png[width=800] + +We pass the ID of the relationship as parameter to extract a node + +[source,cypher] +---- +MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) WITH id(rel) as id +CALL apoc.refactor.extractNode(id,['FooBar'],'FOO','BAR') +YIELD input, output RETURN * +---- + +image::{img}/apoc.refactor.extractNode.png[width=800] + +.Collapse node to relationship + +[source,cypher] +---- +CREATE (f:Foo)-[:FOO {a:1}]->(b:Bar {c:3})-[:BAR {b:2}]->(f) WITH id(b) as id +CALL apoc.refactor.collapseNode(id,'FOOBAR') +YIELD input, output RETURN * +---- + +Before we have this situation + +image::{img}/apoc.refactor.collapseNode.dataset.png[width=800] + +And the result are + +image::{img}/apoc.refactor.collapseNode.png[width=800] + +The property of the two relationship and the property of the node are joined in one relationship that has the properties `a:1`, `b:2`, `name:Bar` + +.Normalize As Boolean + +[source,cypher] +---- +CREATE (:Person {prop: 'Y', name:'A'}),(:Person {prop: 'Yes', name:'B'}),(:Person {prop: 'NO', name:'C'}),(:Person {prop: 'X', name:'D'}) +---- + +As a resul we have four nodes with different properties `prop` like `Y`, `Yes`, `NO`, `X` + +image::{img}/apoc.refactor.normalizeAsBoolean.dataset.png[width=800] + +Now we want to transform some properties into a boolean, `Y`, `Yes` into true and the properties `NO` into false. +The other properties that don't match these possibilities will be set as `null`. + +[source,cypher] +---- +MATCH (n) CALL apoc.refactor.normalizeAsBoolean(n,'prop',['Y','Yes'],['NO']) WITH n ORDER BY n.id RETURN n.prop AS prop +---- + +image::{img}/apoc.refactor.normalizeAsBoolean.png[width=800] + +.Categorize + +First of all we create some nodes as dataset + +[source,cypher] +---- +CREATE (:Person {prop: 'A', k: 'a', id: 1}), + (:Person {prop: 'A', k: 'a', id: 2}), + (:Person {prop: 'C', k: 'c', id: 3}), + (:Person { id: 4}), + (:Person {prop: 'B', k: 'b', id: 5}), + (:Person {prop: 'C', k: 'c', id: 6}) +---- + +As result we have six nodes with label 'Person' with different properties + +image::{img}/apoc.refactor.categorize.dataset.png[width=800] + +Now we want to transform the property `prop` into a separate node with label `Letter` and transfer the properties of the nodes `Person`: `prop` (now renamed in `name`) and `k`. +The nodes `Person` will keep only the propertie `id`, and will be connected with a relationship `IS_A` with the new nodes `Letter`. + +[source,cypher] +---- +CALL apoc.refactor.categorize('prop','IS_A',true,'Letter','name',['k'],1) +---- + +image::{img}/apoc.refactor.categorize.png[width=800] + +The direction of the relationship (in this case outgoing) is defined by the third field, if `true` outgoing else incoming. +If a node doesn't has the property `prop` (like node with `id: 4`) it won't be managed. + +=== Rename + +Procedures set for renaming labels, relationship types, nodes and relationships' properties. +They return the list of eventually impacted constraints and indexes, the user should take care of. + +[cols="1m,5"] +|=== +| call apoc.refactor.rename.label(oldLabel, newLabel, [nodes]) | rename a label from 'oldLabel' to 'newLabel' for all nodes. If 'nodes' is provided renaming is applied to this set only +| call apoc.refactor.rename.type(oldType, newType, [rels]) | rename all relationships with type 'oldType' to 'newType'. If 'rels' is provided renaming is applied to this set only +| call apoc.refactor.rename.nodeProperty(oldName, newName, [nodes]) | rename all node's property from 'oldName' to 'newName'. If 'nodes' is provided renaming is applied to this set only +| call apoc.refactor.rename.typeProperty(oldName, newName, [rels]) | rename all relationship's property from 'oldName' to 'newName'. If 'rels' is provided renaming is applied to this set only +|=== diff --git a/docs/img/apoc.graph.png b/docs/img/apoc.graph.png new file mode 100644 index 0000000000..5dc071b292 Binary files /dev/null and b/docs/img/apoc.graph.png differ diff --git a/docs/index.adoc b/docs/index.adoc index 69b2e0b9a9..afd74c251d 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -155,11 +155,16 @@ $(document).ready(function() { include::create-virtual-nodes-rels.adoc[leveloffset=1] +include::virtual-graph.adoc[leveloffset=1] + +== Graph Refactoring + +include::graph-refactor.adoc[leveloffset=1] + == Triggers include::trigger.adoc[leveloffset=2] -== Appendix : Complete Overview +== Appendix: Complete Overview include::overview.adoc[tags=overview,leveloffset=1] - diff --git a/docs/overview.adoc b/docs/overview.adoc index a14dd094fd..6722d6d32d 100644 --- a/docs/overview.adoc +++ b/docs/overview.adoc @@ -619,272 +619,6 @@ TODO: * merge nodes by label + property * merge relationships -=== Graph Refactoring Examples - -.Clone nodes - -We create a dataset -[source,cypher] ----- -CREATE (f:Foo{name:'Foo'}),(b:Bar{name:'Bar'}) ----- - -As result we have two nodes - -image::{img}/apoc.refactor.cloneNodes.dataset.png[width=800] - -[source,cypher] ----- -MATCH (f:Foo{name:'Foo'}),(b:Bar{name:'Bar'}) WITH f,b -CALL apoc.refactor.cloneNodes([f,b]) yield input, output RETURN * ----- - -As result we have the two nodes that we have created before and their clones - -image::{img}/apoc.refactor.cloneNodes.png[width=800] - -.Clone nodes with relationship - -We create a dataset of two different nodes of type `Actor` connected with other two different node of type `Movie` - -[source,cypher] ----- -CREATE (k:Actor {name:'Keanu Reeves'})-[:ACTED_IN {role:'Neo'}]->(m:Movie {title:'The Matrix'}), - (t:Actor {name:'Tom Hanks'})-[:ACTED_IN {role:'Forrest'}]->(f:Movie {title:'Forrest Gump'}) RETURN * ----- - -image::{img}/apoc.refactor.cloneNodesWithRelationships.dataset.png[width=800] - -[source,cypher] ----- -MATCH (k:Actor {name:'Keanu Reeves'}), (t:Actor {name:'Tom Hanks'}) -CALL apoc.refactor.cloneNodesWithRelationships([k,t]) YIELD input, output RETURN * ----- - -As result we have a copy of the nodes and relationships - -image::{img}/apoc.refactor.cloneNodesWithRelationships.png[width=800] - -.Merge nodes - -We create two nodes with different properties - -[source,cypher] ----- -CREATE (f:Person {name:'Foo'}), (b:Person {surname:'Bar'}) RETURN f,b ----- - -image::{img}/apoc.refactor.mergeNodes.dataset.png[width=800] - -Now we want to merge these nodes into one - -[source,cypher] ----- -MATCH (f:Person {name:'Foo'}), (b:Person {surname:'Bar'}) -CALL apoc.refactor.mergeNodes([f,b]) -YIELD node RETURN node ----- - -image::{img}/apoc.refactor.mergeNodes.png[width=800] - -Thus we have one node with both properties `name` and `surname` - -.Redirect relationship to - -We start with two nodes related each other with a relationship. We create a new node which we will use to redirect the relationship like end node - -[source,cypher] ----- -CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) -CREATE (p:Person {name:'Antony'}) -RETURN * ----- - -image::{img}/apoc.refactor.to.dataset.png[width=800] - -[source,cypher] ----- -MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) with id(rel) as id -MATCH (p:Person {name:'Antony'}) with p as p -MATCH ()-[r]->(), (p:Person) CALL apoc.refactor.to(r, p) YIELD input, output RETURN * ----- - -image::{img}/apoc.refactor.to.png[width=800] - -Now the relationship is towards the new node `Person` - -.Redirect relationship from - -We start with two nodes related each other with a relationship. We create a new node which we will use to redirect the relationship like start node - -[source,cypher] ----- -CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) -CREATE (p:Person {name:'Antony'}) -RETURN * ----- - -image::{img}/apoc.refactor.from.dataset.png[width=800] - -[source,cypher] ----- -MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) with id(rel) as id -MATCH (p:Person {name:'Antony'}) with p as p -MATCH ()-[r]->(), (p:Person) CALL apoc.refactor.from(r, p) YIELD input, output RETURN * ----- - -image::{img}/apoc.refactor.from.png[width=800] - -Now the relationship starts from the new node `Person` from the old node `Bar` - -.Invert relationship - -We start with two nodes connected by a relationship - -[source,cypher] ----- -CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) ----- - -image::{img}/apoc.refactor.invert.dataset.png[width=800] - -Now we want to invert the relationship direction - -[source,cypher] ----- -MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) WITH id(rel) as id -MATCH ()-[r]->() WHERE id(r) = id -CALL apoc.refactor.invert(r) yield input, output RETURN * ----- - -image::{img}/apoc.refactor.invert.call.png[width=800] - -image::{img}/apoc.refactor.invert.png[width=800] - -.Set type - -With a simple relationship between two node - -[source,cypher] ----- -CREATE (f:Foo)-[rel:FOOBAR]->(b:Bar) ----- - -image::{img}/apoc.refactor.setType.dataset.png[width=800] - -We can change the relationship type from `FOOBAR` to `NEW-TYPE` - -[source,cypher] ----- -MATCH (f:Foo)-[rel:FOOBAR]->(b:Bar) with rel -CALL apoc.refactor.setType(rel, 'NEW-TYPE') YIELD input, output RETURN * ----- - -image::{img}/apoc.refactor.setType.png[width=800] - -.Extract node from relationships - -[source,cypher] ----- -CREATE (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) ----- - -image::{img}/apoc.refactor.extractNode.dataset.png[width=800] - -We pass the ID of the relationship as parameter to extract a node - -[source,cypher] ----- -MATCH (f:Foo)-[rel:FOOBAR {a:1}]->(b:Bar) WITH id(rel) as id -CALL apoc.refactor.extractNode(id,['FooBar'],'FOO','BAR') -YIELD input, output RETURN * ----- - -image::{img}/apoc.refactor.extractNode.png[width=800] - -.Collapse node to relationship - -[source,cypher] ----- -CREATE (f:Foo)-[:FOO {a:1}]->(b:Bar {c:3})-[:BAR {b:2}]->(f) WITH id(b) as id -CALL apoc.refactor.collapseNode(id,'FOOBAR') -YIELD input, output RETURN * ----- - -Before we have this situation - -image::{img}/apoc.refactor.collapseNode.dataset.png[width=800] - -And the result are - -image::{img}/apoc.refactor.collapseNode.png[width=800] - -The property of the two relationship and the property of the node are joined in one relationship that has the properties `a:1`, `b:2`, `name:Bar` - -.Normalize As Boolean - -[source,cypher] ----- -CREATE (:Person {prop: 'Y', name:'A'}),(:Person {prop: 'Yes', name:'B'}),(:Person {prop: 'NO', name:'C'}),(:Person {prop: 'X', name:'D'}) ----- - -As a resul we have four nodes with different properties `prop` like `Y`, `Yes`, `NO`, `X` - -image::{img}/apoc.refactor.normalizeAsBoolean.dataset.png[width=800] - -Now we want to transform some properties into a boolean, `Y`, `Yes` into true and the properties `NO` into false. -The other properties that don't match these possibilities will be set as `null`. - -[source,cypher] ----- -MATCH (n) CALL apoc.refactor.normalizeAsBoolean(n,'prop',['Y','Yes'],['NO']) WITH n ORDER BY n.id RETURN n.prop AS prop ----- - -image::{img}/apoc.refactor.normalizeAsBoolean.png[width=800] - -.Categorize - -First of all we create some nodes as dataset - -[source,cypher] ----- -CREATE (:Person {prop: 'A', k: 'a', id: 1}), - (:Person {prop: 'A', k: 'a', id: 2}), - (:Person {prop: 'C', k: 'c', id: 3}), - (:Person { id: 4}), - (:Person {prop: 'B', k: 'b', id: 5}), - (:Person {prop: 'C', k: 'c', id: 6}) ----- - -As result we have six nodes with label 'Person' with different properties - -image::{img}/apoc.refactor.categorize.dataset.png[width=800] - -Now we want to transform the property `prop` into a separate node with label `Letter` and transfer the properties of the nodes `Person`: `prop` (now renamed in `name`) and `k`. -The nodes `Person` will keep only the propertie `id`, and will be connected with a relationship `IS_A` with the new nodes `Letter`. - -[source,cypher] ----- -CALL apoc.refactor.categorize('prop','IS_A',true,'Letter','name',['k'],1) ----- - -image::{img}/apoc.refactor.categorize.png[width=800] - -The direction of the relationship (in this case outgoing) is defined by the third field, if `true` outgoing else incoming. -If a node doesn't has the property `prop` (like node with `id: 4`) it won't be managed. - -=== Rename - -Procedures set for renaming labels, relationship types, nodes and relationships' properties. -They return the list of eventually impacted constraints and indexes, the user should take care of. - -[cols="1m,5"] -|=== -| call apoc.refactor.rename.label(oldLabel, newLabel, [nodes]) | rename a label from 'oldLabel' to 'newLabel' for all nodes. If 'nodes' is provided renaming is applied to this set only -| call apoc.refactor.rename.type(oldType, newType, [rels]) | rename all relationships with type 'oldType' to 'newType'. If 'rels' is provided renaming is applied to this set only -| call apoc.refactor.rename.nodeProperty(oldName, newName, [nodes]) | rename all node's property from 'oldName' to 'newName'. If 'nodes' is provided renaming is applied to this set only -| call apoc.refactor.rename.typeProperty(oldName, newName, [rels]) | rename all relationship's property from 'oldName' to 'newName'. If 'rels' is provided renaming is applied to this set only -|=== == Spatial diff --git a/docs/virtual-graph.adoc b/docs/virtual-graph.adoc new file mode 100644 index 0000000000..9a8a650e70 --- /dev/null +++ b/docs/virtual-graph.adoc @@ -0,0 +1,62 @@ +== Virtual Graph + +Create a graph object (map) from information that's passed in. +It's basic structure is: `{name:"Name",properties:{properties},nodes:[nodes],relationships:[relationships]}` + +[cols="1m,5"] +|=== +| apoc.graph.from(data,'name',{properties}) yield graph | creates a virtual graph object for later processing it tries its best to extract the graph information from the data you pass in +| apoc.graph.fromData([nodes],[relationships],'name',{properties}) | creates a virtual graph object for later processing +| apoc.graph.fromPaths(path,'name',{properties}) | creates a virtual graph object for later processing +| apoc.graph.fromPaths([paths],'name',{properties}) | creates a virtual graph object for later processing +| apoc.graph.fromDB('name',{properties}) | creates a virtual graph object for later processing +| apoc.graph.fromCypher('statement',{params},'name',{properties}) | creates a virtual graph object for later processing +|=== + +=== Virtual Graph Examples + +We create a dataset for our examples + +[source,cypher] +---- +CREATE (a:Actor {name:'Tom Hanks'})-[r:ACTED_IN {roles:'Forrest'}]->(m:Movie {title:'Forrest Gump'}) RETURN * +---- + +.Virtual graph from data + +[source,cypher] +---- +MATCH (n)-[r]->(m) CALL apoc.graph.fromData([n,m],[r],'test',{answer:42}) YIELD graph RETURN * +---- + +.Virtual graph from path + +[source,cypher] +---- +MATCH path = (n)-[r]->(m) CALL apoc.graph.fromPath(path,'test',{answer:42}) YIELD graph RETURN * +---- + +.Virtual graph from paths + +[source,cypher] +---- +MATCH path = (n)-[r]->(m) CALL apoc.graph.fromPaths([path],'test',{answer:42}) YIELD graph RETURN * +---- + +.Virtual graph from DB + +[source,cypher] +---- +CALL apoc.graph.fromDB('test',{answer:42}) YIELD graph RETURN * +---- + +.Virtual graph from Cypher + +[source,cypher] +---- +CALL apoc.graph.fromCypher('MATCH (n)-[r]->(m) RETURN *',null,'test',{answer:42}) YIELD graph RETURN * +---- + +As a result we have a virtual graph object for later processing + +image::{img}/apoc.graph.png[width=800] \ No newline at end of file