## 15.1. Virtual Nodes/Rels

This section describes procedures that create virtual nodes and relationships.

Virtual Nodes and Relationships don’t exist in the graph, they are only returned to the UI/user for representing a graph projection. They can be visualized or processed otherwise. Please note that they have negative id’s.

### 15.1.1. Function Overview

`CALL apoc.create.vNode(['Label'], {key:value,…​}) YIELD node` | returns a virtual node
`apoc.create.vNode(['Label'], {key:value,…​})` | function returns a virtual node
`CALL apoc.create.vNodes(['Label'], [{key:value,…​}])` | returns virtual nodes
`apoc.create.virtual.fromNode(node, [propertyNames])` | function returns a virtual node built from an existing node with only the requested properties
`CALL apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,…​}, nodeTo) YIELD rel` | returns a virtual relationship
`apoc.create.vRelationship(nodeFrom,'KNOWS',{key:value,…​}, nodeTo)` | function returns a virtual relationship
`CALL apoc.create.vPattern({_labels:['LabelA'],key:value},'KNOWS',{key:value,…​}, {_labels:['LabelB'],key:value})` | returns a virtual pattern
`CALL apoc.create.vPatternFull(['LabelA'],{key:value},'KNOWS',{key:value,…​},['LabelB'],{key:value})` | returns a virtual pattern

### 15.1.2. Virtual Nodes/Rels Example

Simple example aggregate Relationships.

    MATCH (from:Account)-[:SENT]->(p:Payment)-[:RECEIVED]->(to:Account)
    RETURN from, to, apoc.create.vRelationship(from,'PAID',{amount:sum(p.amount)},to) as rel;

Example with virtual node lookups, people grouped by their countries.

    MATCH (p:Person) WITH collect(distinct p.country) as countries
    WITH [cName IN countries | apoc.create.vNode(['Country'],{name:cName})] as countryNodes
    WITH apoc.coll.groupBy(countryNodes,'name') as countries
    MATCH (p1:Person)-[:KNOWS]->(p2:Person)
    WITH p1.country as cFrom, p2.country as cTo, count(*) as count, countries
    RETURN countries[cFrom] as from, countries[cTo] as to, apoc.create.vRelationship(countries[cFrom],'KNOWS',{count:count},countries[cTo]) as rel;

That’s of course easier with `apoc.nodes.group`.

From a simple dataset

    CREATE(a:Person)-[r:ACTED_IN]->(b:Movie)

We can create a virtual copy, adding as attribute `name` the labels value

    MATCH (a)-[r]->(b)
    WITH head(labels(a)) AS l, head(labels(b)) AS l2, type(r) AS rel_type, count(*) as count
    CALL apoc.create.vNode([l],{name:l}) yield node as a
    CALL apoc.create.vNode([l2],{name:l2}) yield node as b
    CALL apoc.create.vRelationship(a,rel_type,{count:count},b) yield rel
    RETURN *;

| Virtual nodes and virtual relationships have always a negative id

Virtual nodes can also be built from existing nodes, filtering the properties in order to get a subset of them. In this case, the Virtual node keeps the id of the original node.

    MATCH (node:Person {name:'neo', age:'42'})
    return apoc.create.virtual.fromNode(node, ['name']) as person

Virtual pattern `vPattern`.

    CALL apoc.create.vPattern({_labels:['Person'],name:'Mary'},'KNOWS',{since:2012},{_labels:['Person'],name:'Michael'})

We can add more labels, just adding them on `_labels`

    CALL apoc.create.vPattern({_labels:['Person', 'Woman'],name:'Mary'},'KNOWS',{since:2012},{_labels:['Person', 'Man'],name:'Michael'})

Virtual pattern full `vPatternFull`.

    CALL apoc.create.vPatternFull(['British','Person'],{name:'James', age:28},'KNOWS',{since:2009},['Swedish','Person'],{name:'Daniel', age:30})

We can create a virtual pattern from an existing one

    CREATE(a:Person {name:'Daniel'})-[r:KNOWS]->(b:Person {name:'John'})

From this dataset we can create a virtual pattern

    MATCH (a)-[r]->(b)
    WITH head(labels(a)) AS labelA, head(labels(b)) AS labelB, type(r) AS rel_type, a.name AS aName, b.name AS bName
    CALL apoc.create.vPatternFull([labelA],{name: aName},rel_type,{since:2009},[labelB],{name: bName}) yield from, rel, to
    RETURN *;