jsonb operators and functions#
Properties are jsonb. A vertex is the composite (id graphid, properties jsonb), and
an edge adds start and end. So every property you read in Cypher, and every property
column you touch in SQL, is a value of PostgreSQL’s jsonb type. That has two consequences
to take in before anything else on this page. A string property comes back quoted, and the
whole jsonb operator set applies to it.
Examples run against the example dataset.
Property access operators#
These four take a jsonb value on the left. The two with a doubled arrow return text; the
two with a single arrow return jsonb.
Operator |
Right operand |
Returns |
Result |
|---|---|---|---|
|
|
|
The value at that key or index |
|
|
|
The same value, with a string unquoted |
|
|
|
The value at that path |
|
|
|
The same value, with a string unquoted |
SELECT properties -> 'name' AS quoted, properties ->> 'name' AS plain
FROM moviekb.person ORDER BY id LIMIT 1;
quoted | plain
----------------+--------------
"Keanu Reeves" | Keanu Reeves
SELECT '{"a":{"b":[1,2]}}'::jsonb #> '{a,b}' AS at_path,
'{"a":{"b":[1,2]}}'::jsonb #>> '{a,b,0}' AS at_path_text;
at_path | at_path_text
---------+--------------
[1, 2] | 1
Getting an unquoted scalar out#
A Cypher RETURN boxes a scalar output column to jsonb. So RETURN m.title prints
"Inception", quotes included, and so does RETURN m.title::text. The quotes are part of
the JSON, not an artefact of psql.
The way out is #>> '{}': an empty path selects the whole document, and #>> returns it as
text. Wrap the Cypher in SQL and apply it there:
SELECT t AS boxed, t #>> '{}' AS unboxed
FROM (MATCH (m:movie {title:'Inception'}) RETURN m.title AS t) q;
boxed | unboxed
-------------+-----------
"Inception" | Inception
Reach for this idiom whenever a Cypher result has to join against, or be compared with,
ordinary text. A join onto a relational table is one such case.
Containment and existence operators#
Operator |
Right operand |
Returns true when |
|---|---|---|
|
|
The left document contains the right one |
|
|
The left document is contained in the right one |
|
|
The key or string element is present |
`? |
` |
|
|
|
All of the keys are present |
SELECT properties @> '{"name":"Keanu Reeves"}' AS contains,
properties ? 'born' AS has_born,
properties ?& ARRAY['name','born'] AS has_both
FROM moviekb.person ORDER BY id LIMIT 1;
contains | has_born | has_both
----------+----------+----------
t | t | t
A property index cannot serve any of these; each is a containment test over the whole bag
rather than a comparison against one key. A GIN index on properties can.
Modification operators#
Operator |
Right operand |
Result |
|---|---|---|
|
|
The two documents merged, the right winning on a shared key |
|
|
The document without that key or element |
|
|
The document without the value at that path |
SELECT '{"a":1}'::jsonb || '{"b":2}'::jsonb AS merged,
'{"a":1,"b":2}'::jsonb - 'a' AS without_a,
'{"a":{"b":1}}'::jsonb #- '{a,b}' AS without_path;
merged | without_a | without_path
------------------+-----------+--------------
{"a": 1, "b": 2} | {"b": 2} | {"a": {}}
In Cypher these are spelled as clauses instead: SET n += {city:'Seoul'} merges, and
REMOVE n.city deletes a key.
Arithmetic operators on jsonb#
PostgreSQL already gives - and || a meaning on jsonb, so AgensGraph could not reuse
them for arithmetic. It adds eight operators whose names are wrapped in backticks. The
Cypher parser emits those when you write +, -, *, /, % or ^ on properties.
Operator |
Implemented by |
OID |
Meaning |
|---|---|---|---|
|
|
7176 |
Addition; concatenation for two strings or two lists |
|
|
7178 |
Subtraction |
|
|
7180 |
Multiplication |
|
|
7182 |
Division, integer when both operands are integers |
|
|
7184 |
Modulo |
|
|
7186 |
Exponentiation |
|
|
7190 |
Unary plus |
|
|
7191 |
Unary minus |
Backtick is an operator character in this fork’s lexer (scan.l:369), so these names are
typeable in SQL as well. Both operands must be jsonb:
SELECT '"a"'::jsonb `+` '"b"'::jsonb AS strings, '[1]'::jsonb `+` '[2]'::jsonb AS lists;
strings | lists
---------+--------
"ab" | [1, 2]
Note
Write Cypher arithmetic with the ordinary operators — n.a + n.b — and let the parser choose
these. The backtick names are here so you can recognise them in an EXPLAIN plan, in an
error message, or in a pg_operator listing. The implementation functions can also be called
directly: jsonb_add('1'::jsonb, '2'::jsonb) returns 3.
ag_property_text#
Added in 2.18
ag_property_text ( properties jsonb, key text, expected_kind "char" ) -> text
Returns a property’s value as text, but only when the stored value is of the JSON kind
asked for.
Name |
Type |
Description |
|---|---|---|
|
|
The property bag to read. |
|
|
Name of the property. |
|
|
|
Considerations
This backs typed-column property promotion.
->>returns the text of a value, and text carries no kind, so the string"5"and the number5are indistinguishable through it. A column of integers would answer 5 for a property that holds a string. This function asks the kind first.A kind mismatch raises, it does not return null:
property "n" holds a numberwith the detailA column promoting this property holds strings.A property that is absent, or present and JSON null, returns null.
It is not intended to be written by hand. Read a property with
n.keyin Cypher orproperties ->> 'key'in SQL.
SELECT ag_property_text('{"name":"x","n":5}'::jsonb, 'n', 'n') AS number,
ag_property_text('{"name":"x"}'::jsonb, 'missing', 's') AS absent;
number | absent
--------+--------
5 |
cypher_to_jsonb#
cypher_to_jsonb ( value anyelement ) -> jsonb
Boxes a value to jsonb using Cypher’s rules, which differ from to_jsonb’s for string
types.
Considerations
This is the function a Cypher
RETURNinserts around a scalar output column. It appears in a plan at the point where the boxing happens.It is strict, so a null stays null.
The parser refuses to wrap a graph element in it. That refusal makes
coalesce(m, 0)and ajsonblist of vertices fail. It lives in the parser’scoerce_to_jsonb(), not in the function. Called by name on avertex, the function returns the whole composite,idandtidincluded, which is not the property bag thatv::jsonbgives you. Usevertex_to_jsonbor the cast for that.
SELECT cypher_to_jsonb('abc'::text) AS from_text, cypher_to_jsonb(42) AS from_integer;
from_text | from_integer
-----------+--------------
"abc" | 42
edge_to_jsonb#
edge_to_jsonb ( e edge ) -> jsonb
Returns an edge’s property bag. This is the function the cast e::jsonb uses, and it
returns the same value as properties(e).
Considerations
MATCH ()-[e:related_to]->() WITH e LIMIT 1 RETURN edge_to_jsonb(e) AS bag;
bag
---------------
{"weight": 1}
graphid_in_jsonb_array#
graphid_in_jsonb_array ( id graphid, elements jsonb ) -> boolean
Returns true when a graphid is one of the graph elements in a jsonb array.
Considerations
The parser emits this for
id(n) IN [...]so that a list of elements can be tested without unboxing each one. Agraphidboxes to ajsonbstring, and this compares against that form.Written by hand it is a plain membership test.
SELECT graphid_in_jsonb_array('4.1'::graphid, '["4.1","4.2"]'::jsonb) AS present;
present
---------
t
jsonb_array_length#
jsonb_array_length ( list jsonb ) -> integer
Returns the number of elements in a jsonb array, as an integer.
Considerations
PostgreSQL’s own function.
cardinalityandlengthare the Cypher spellings and returnjsonb; this one returns a nativeintegerand raises on a value that is not an array.
SELECT jsonb_array_length('[1,2,3]');
jsonb_array_length
--------------------
3
to_jsonb#
to_jsonb ( value anyelement ) -> jsonb
Converts any SQL value to jsonb, mapping a composite to an object and an array to an
array.
Considerations
This is how you get relational rows into a graph.
LOAD FROM t AS r CREATE (:label = to_jsonb(row_to_json(r)))turns each row into a property bag.On a graph element it converts the whole composite, not the property bag.
v::jsonbandvertex_to_jsonbgive the bag.
SELECT to_jsonb(42) AS number, to_jsonb('x'::text) AS string;
number | string
--------+--------
42 | "x"
vertex_to_jsonb#
vertex_to_jsonb ( v vertex ) -> jsonb
Returns a vertex’s property bag. This is the function the cast v::jsonb uses, and it
returns the same value as properties(v).
MATCH (n:person {name:'Keanu Reeves'}) RETURN vertex_to_jsonb(n) AS bag;
bag
----------------------------------------
{"born": 1964, "name": "Keanu Reeves"}
Ordering and comparison#
jsonb has its own total order, and it is not the order the values would have in their own
types. Kind comes first: null, then string, then number, then boolean, then array, then
object. Only values of the same kind compare by value:
SELECT v FROM (VALUES ('1'::jsonb),('"a"'),('true'),('null'),('[1]'),('{"k":1}')) t(v) ORDER BY v;
v
----------
null
"a"
1
true
[1]
{"k": 1}
This matters wherever a Cypher result is sorted or aggregated. min() and max() over a
jsonb column follow that order, so a property that holds numbers in some rows and strings
in others will not sort the way a reader expects. Read it through a typed column, or cast it,
when the order has to be the type’s own.
Two integers also divide as integers, because jsonb keeps the distinction:
RETURN 7 / 2 AS integer_division, 7.0 / 2 AS real_division;
integer_division | real_division
------------------+--------------------
3 | 3.5000000000000000
See also#
All functions — every function in one table
Scalar functions —
properties,keys,lengthType conversion functions — the
to…familyCombining SQL and Cypher — where
#>> '{}'earns its keep