CALL subqueries in transactions
CALL
subqueries can be made to execute in separate, inner transactions, producing intermediate commits.
This can be useful when doing large write operations, like batch updates, imports, and deletes.
To execute a CALL
subquery in separate transactions, add the modifier IN TRANSACTIONS
after the subquery.
An outer transaction is opened to report back the accumulated statistics for the inner transactions
(created and deleted nodes, relationships, etc.) and it will succeed or fail depending on the results
of those inner transactions.
By default, inner transactions group together batches of 1000 rows.
Cancelling the outer transaction will cancel the inner ones as well.
|
Loading CSV data
This example uses a CSV file and the LOAD CSV
clause to import data into the database.
It creates nodes in separate transactions using CALL { … } IN TRANSACTIONS
:
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
LOAD CSV FROM 'file:///friends.csv' AS line
CALL {
WITH line
CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS
|
Rows: 0 |
As the size of the CSV file in this example is small, only a single separate transaction is started and committed.
Deleting a large volume of data
Using CALL { … } IN TRANSACTIONS
is the recommended way of deleting a large volume of data.
MATCH (n)
CALL {
WITH n
DETACH DELETE n
} IN TRANSACTIONS
|
Rows: 0 |
The CALL { … } IN TRANSACTIONS
subquery should not be modified.
Any necessary filtering can be done before the subquery.
MATCH (n:Label) WHERE n.prop > 100
CALL {
WITH n
DETACH DELETE n
} IN TRANSACTIONS
|
Rows: 0 |
Batching
The amount of work to do in each separate transaction can be specified in terms of how many input rows
to process before committing the current transaction and starting a new one.
The number of input rows is set with the modifier OF n ROWS
(or OF n ROW
).
If omitted, the default batch size is 1000
rows.
The number of rows can be expressed using any expression that evaluates to a positive integer and does not refer to nodes or relationships.
This example loads a CSV file with one transaction for every 2
input rows:
1,Bill,26
2,Max,27
3,Anna,22
4,Gladys,29
5,Summer,24
LOAD CSV FROM 'file:///friends.csv' AS line
CALL {
WITH line
CREATE (:Person {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS OF 2 ROWS
|
Rows: 0 |
The query now starts and commits three separate transactions:
-
The first two executions of the subquery (for the first two input rows from
LOAD CSV
) take place in the first transaction. -
The first transaction is then committed before proceeding.
-
The next two executions of the subquery (for the next two input rows) take place in a second transaction.
-
The second transaction is committed.
-
The last execution of the subquery (for the last input row) takes place in a third transaction.
-
The third transaction is committed.
You can also use CALL { … } IN TRANSACTIONS OF n ROWS
to delete all your data in batches in order to avoid a huge garbage collection or an OutOfMemory
exception.
For example:
MATCH (n)
CALL {
WITH n
DETACH DELETE n
} IN TRANSACTIONS OF 2 ROWS
|
Rows: 0 |
Up to a point, using a larger batch size will be more performant.
The batch size of |
Error behavior
This feature was introduced in Neo4j 5.7.
Users can choose one of three different option flags to control the behavior
in case of an error occurring in any of the inner transactions of CALL { … } IN TRANSACTIONS
:
-
ON ERROR CONTINUE
to ignore a recoverable error and continue the execution of subsequent inner transactions. The outer transaction succeeds. It will cause the expected variables from the failed inner query to be bound as null for that specific transaction. -
ON ERROR BREAK
to ignore a recoverable error and stop the execution of subsequent inner transactions. The outer transaction succeeds. It will cause expected variables from the failed inner query to be bound as null for all onward transactions (including the failed one). -
ON ERROR FAIL
to acknowledge a recoverable error and stop the execution of subsequent inner transactions. The outer transaction fails. This is the default behavior if no flag is explicitly specified.
On error, any previously committed inner transactions remain committed, and are not rolled back. Any failed inner transactions are rolled back. |
In the following example, the last subquery execution in the second inner transaction fails due to division by zero.
UNWIND [4, 2, 1, 0] AS i
CALL {
WITH i
CREATE (:Person {num: 100/i})
} IN TRANSACTIONS OF 2 ROWS
RETURN i
/ by zero (Transactions committed: 1)
When the failure occurred, the first transaction had already been committed, so the database contains two example nodes.
MATCH (e:Person)
RETURN e.num
e.num |
---|
|
|
Rows: 2 |
In the following example, ON ERROR CONTINUE
is used after a failed inner transaction to execute the remaining inner transactions and not fail the outer transaction:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR CONTINUE
RETURN n.num;
n.num |
---|
|
|
|
|
Rows: 4 |
Note the difference in results when batching in transactions of 2 rows:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 2 ROWS
ON ERROR CONTINUE
RETURN n.num;
n.num |
---|
|
|
|
|
Rows: 4 |
This happens because an inner transaction with the two first i
elements (1 and 0)
was created, and it fails for 0.
This causes it to be rolled back and the return
variable is filled with nulls for those two elements.
In the following example, ON ERROR BREAK
is used after a failed inner transaction to not execute the remaining inner transaction and not fail the outer transaction:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR BREAK
RETURN n.num;
n.num |
---|
|
|
|
|
Rows: 4 |
Note the difference in results when batching in transactions of 2 rows:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 2 ROWS
ON ERROR BREAK
RETURN n.num;
n.num |
---|
|
|
|
|
Rows: 4 |
In the following example, ON ERROR FAIL
is used after the failed inner transaction, to not execute the remaining inner transactions and to fail the outer transaction:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR FAIL
RETURN n.num;
/ by zero (Transactions committed: 1)
Status report
Users can also report the execution status of the inner transactions by using REPORT STATUS AS var
.
This flag is disallowed for ON ERROR FAIL
. For more information, see Error behavior.
After each execution of the inner query finishes (successfully or not), a status value is created that records information about the execution and the transaction that executed it:
-
If the inner execution produces one or more rows as output, then a binding to this status value is added to each row, under the selected variable name.
-
If the inner execution fails then a single row is produced containing a binding to this status value under the selected variable, and null bindings for all variables that should have been returned by the inner query (if any).
The status value is a map value with the following fields:
-
started
:true
when the inner transaction was started,false
otherwise. -
committed
,true
when the inner transaction changes were successfully committed,false
otherwise. -
transactionId
: the inner transaction id, ornull
if the transaction was not started. -
errorMessage
, the inner transaction error message, ornull
in case of no error.
Example of reporting status with ON ERROR CONTINUE
:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR CONTINUE
REPORT STATUS AS s
RETURN n.num, s;
n.num | s |
---|---|
|
|
|
|
|
|
|
|
Rows: 4 |
Example of reporting status with ON ERROR BREAK
:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR BREAK
REPORT STATUS AS s
RETURN n.num, s.started, s.committed, s.errorMessage;
n.num | s.started | s.committed | s.errorMessage |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 4 |
Reporting status with ON ERROR FAIL
is disallowed:
UNWIND [1, 0, 2, 4] AS i
CALL {
WITH i
CREATE (n:Person {num: 100/i}) // Note, fails when i = 0
RETURN n
} IN TRANSACTIONS
OF 1 ROW
ON ERROR FAIL
REPORT STATUS AS s
RETURN n.num, s.errorMessage;
REPORT STATUS can only be used when specifying ON ERROR CONTINUE or ON ERROR BREAK
Restrictions
These are the restrictions on queries that use CALL { … } IN TRANSACTIONS
:
-
A nested
CALL { … } IN TRANSACTIONS
inside aCALL { … }
clause is not supported. -
A
CALL { … } IN TRANSACTIONS
in aUNION
is not supported. -
A
CALL { … } IN TRANSACTIONS
after a write clause is not supported, unless that write clause is inside aCALL { … } IN TRANSACTIONS
.
Additionally, there are some restrictions that apply when using an importing WITH
clause in a CALL
subquery:
-
Only variables imported with the importing
WITH
clause can be used. -
No expressions or aliasing are allowed within the importing
WITH
clause. -
It is not possible to follow an importing
WITH
clause with any of the following clauses:DISTINCT
,ORDER BY
,WHERE
,SKIP
, andLIMIT
.
Attempting any of the above, will throw an error.
For example, the following query using a WHERE
clause after an importing WITH
clause will throw an error:
UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l
CALL {
WITH l
WHERE size(l) > 2
RETURN l AS largeLists
}
RETURN largeLists
Importing WITH should consist only of simple references to outside variables.
WHERE is not allowed.
A solution to this restriction, necessary for any filtering or ordering of an importing WITH
clause, is to declare a second WITH
clause after the importing WITH
clause.
This second WITH
clause will act as a regular WITH
clause.
For example, the following query will not throw an error:
UNWIND [[1,2],[1,2,3,4],[1,2,3,4,5]] AS l
CALL {
WITH l
WITH size(l) AS size, l AS l
WHERE size > 2
RETURN l AS largeLists
}
RETURN largeLists
largeLists |
---|
|
|
Rows: 2 |
Was this page helpful?