Manage schemaless data
Stay organized with collections
Save and categorize content based on your preferences.
This page explains how to manage schemaless data in Spanner Graph. It also provides best practices and troubleshooting tips. We recommend that you are familiar with the Spanner Graph schema and queries.
Schemaless data management lets you create a flexible graph definition. You can add, update, or delete node and edge type definitions without schema changes. This approach supports iterative development and reduces schema management overhead, while preserving the familiar graph query experience.
Schemaless data management is useful for the following scenarios:
Managing graphs with frequent changes, such as updates and additions of element labels and properties.
Graphs with many node and edge types, which makes the creation and management of input tables cumbersome.
For more information about when to use schemaless data management, see Considerations for schemaless data management.
Model schemaless data
Spanner Graph lets you create a graph from tables that maps rows to
nodes and edges.
Instead of using separate tables for each element type, schemaless data modeling
typically employs a single node table and a single edge table with a STRING
column for the label and a JSON
column for properties.
Create input tables
You can create a single GraphNode
table and a single GraphEdge
table to
store schemaless data, as shown in the following example. Table names are for
illustrative purposes—you can choose your own.
CREATETABLEGraphNode(
idINT64NOTNULL,
labelSTRING(MAX)NOTNULL,
propertiesJSON,
)PRIMARYKEY(id);
CREATETABLEGraphEdge(
idINT64NOTNULL,
dest_idINT64NOTNULL,
edge_idINT64NOTNULL,
labelSTRING(MAX)NOTNULL,
propertiesJSON,
)PRIMARYKEY(id,dest_id,edge_id),
INTERLEAVEINPARENTGraphNode;
This example performs the following actions:
Stores all nodes in a single table,
GraphNode
, identified by a uniqueid
.Stores all edges in a single table,
GraphEdge
, identified by a unique combination of source (id
), destination (dest_id
), and its own identifier (edge_id
). Anedge_id
is included as part of the primary key to permit more than one edge from anid
to adest_id
pair.
Both the node and edge tables have their own label
and properties
columns.
These columns are of type STRING
and JSON
, respectively.
For more information about key choices for schemaless data management, see Primary key definitions for nodes and edges.
Create a property graph
The CREATE PROPERTY GRAPH statement maps the input tables in the previous section as nodes and edges. Use the following clauses to define labels and properties for schemaless data:
DYNAMIC LABEL
: creates the label of a node or an edge from aSTRING
column that's from the input table.DYNAMIC PROPERTIES
: creates properties of a node or an edge from aJSON
column that's from the input table.
The following example shows how to create a graph using these clauses:
CREATEPROPERTYGRAPHFinGraph
NODETABLES(
GraphNode
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
)
EDGETABLES(
GraphEdge
SOURCEKEY(id)REFERENCESGraphNode(id)
DESTINATIONKEY(dest_id)REFERENCESGraphNode(id)
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
);
Define dynamic labels
The DYNAMIC LABEL
clause designates a STRING
data type column to store the
label values.
For example, in a GraphNode
row, if the label
column has a person
value,
it maps to a Person
node within the graph. Likewise, in a GraphEdge
row, if
the label column has a value of owns
, it maps to an Owns
edge within the
graph.
Mapping a GraphNode label to a GraphEdge label
For more information about limitations when using dynamic labels, see Limitations.
Define dynamic properties
The DYNAMIC PROPERTIES
clause designates a JSON
data type column to store
properties. JSON keys represent property names, and JSON values represent
property values.
For example, when a GraphNode
row's properties
column has the JSON value
'{"name": "David", "age": 43}'
, Spanner maps it to a node that
has age
and name
properties with 43
and "David"
as their respective
values.
Considerations for schemaless data management
You might not want to use schemaless data management in the following scenarios:
- The node and edge types for your graph data are well defined, or their labels and properties don't require frequent updates.
- Your data is already stored in Spanner, and you prefer to build graphs from existing tables instead of introducing new, dedicated node and edge tables.
- The limitations of schemaless data prevent adoption.
In addition, if your workload is highly sensitive to write performance,
especially when properties are frequently updated, using schema defined
properties with
primitive data types such as STRING
or INT64
is more effective than using
dynamic properties with the JSON
type.
For more information about how to define the graph schema without using dynamic data labels and properties, see the Spanner Graph schema overview.
Query schemaless graph data
You can query schemaless graph data using Graph Query Language (GQL). You can use the sample queries in the Spanner Graph Query overview and GQL reference with limited modifications.
Match nodes and edges using labels
You can match nodes and edges by using the label expression in GQL.
The following query matches connected nodes and edges that have the values
account
and transfers
in their label column.
GRAPHFinGraph
MATCH(a:Account{id:1})-[t:Transfers]->(d:Account)
RETURNCOUNT(*)ASresult_count;
Access properties
Spanner models top-level keys and values of the JSON
data type
as properties, such as age
and name
in the following example.
JSON document
Properties
{
"name": "Tom",
"age": 43,
}
"name": "Tom"
"age": 34
The following example shows how to access the property name
from the Person
node.
GRAPHFinGraph
MATCH(person:Person{id:1})
RETURNperson.name;
The query returns results similar to the following:
JSON"Tom"
Convert property data types
Spanner treats properties as values of the JSON data type. In some cases, such as for comparisons with SQL types, you must first convert properties to a SQL type.
In the following example, the query performs the following data type conversions:
- Converts the
is_blocked
property to a boolean type to evaluate the expression. - Converts the
order_number_str
property to a string type and compares it with the literal value"302290001255747"
. - Uses LAX_INT64
function to safely convert
order_number_str
to an integer as the return type.
GRAPHFinGraph
MATCH(a:Account)-[t:Transfers]->()
WHEREBOOL(a.is_blocked)ANDSTRING(t.order_number_str)="302290001255747"
RETURNLAX_INT64(t.order_number_str)ASorder_number_as_int64;
This returns results similar to the following:
+-----------------------+
|order_number_as_int64|
+-----------------------+
|302290001255747|
+-----------------------+
In clauses such as GROUP BY
and ORDER BY
, you must also convert the JSON
data type. The following example converts the city
property to a string type,
which allows you to use it for grouping.
GRAPHFinGraph
MATCH(person:Person{country:"South Korea"})
RETURNSTRING(person.city)asperson_city,COUNT(*)ascnt
LIMIT10
Tips for converting JSON data types to SQL data types:
- Strict converters, such as
INT64
, perform rigorous type and value checks. Use strict converters when the JSON data type is known and enforced, for example, by using schema constraints to enforce the property data type. - Flexible converters, such as
LAX_INT64
, convert the value safely when possible, and returnNULL
when conversion isn't feasible. Use flexible converters when a rigorous check isn't required or types are difficult to enforce.
For more information about data conversion, see troubleshooting tips.
Filter by property values
In
property filters,
Spanner treats the filter parameters as values of JSON
data
type. For example, in the following query, Spanner
treatsis_blocked
as a JSON boolean
and order_number_str
as a JSON
string
.
GRAPHFinGraph
MATCH(a:Account{is_blocked:false})-[t:Transfers{order_number_str:"302290001255747"}]->()
RETURNa.idASaccount_id;
This returns results similar to the following:
+-----------------------+
|account_id|
+-----------------------+
|7|
+-----------------------+
The filter parameter must match the property type and value. For example, when
the order_number_str
filter parameter is an integer, Spanner
finds no match because the property is a JSON string
.
GRAPHFinGraph
MATCH(a:Account{is_blocked:false})-[t:Transfers{order_number_str:302290001255747}]->()
RETURNt.order_number_str;
Access nested JSON properties
Spanner doesn't model nested JSON keys and values as properties.
In the following example, Spanner doesn't model the JSON keys
city
, state
, and country
as properties because they are nested under
location
. However, you can access them with a JSON
field access operator
or a JSON
subscript operator.
JSON document
Properties
{
"name": "Tom",
"age": 43,
"location": {
"city": "New York",
"state": "NY",
"country": "USA",
}
}
"name": "Tom"
"age": 34
"location": {
"city": "New York",
"state": "NY",
"country": "USA",
}
The following example shows how to access nested properties with the JSON field access operator.
GRAPHFinGraph
MATCH(person:Person{id:1})
RETURNSTRING(person.location.city);
This returns results similar to the following:
"New York"
Modify schemaless data
Spanner Graph maps data from tables to graph nodes and edges. When you change input table data, this change directly causes mutations to the corresponding graph data. For more information about graph data mutation, see Insert, update, or delete Spanner Graph data.
Example queries
This section provides examples that show how to create, update, and delete graph data.
Insert graph data
The following example inserts a person
node. Label and property names must
use lowercase.
INSERTINTOGraphNode(id,label,properties)
VALUES(4,"person",JSON'{"name": "David", "age": 43}');
Update graph data
The following example updates an Account
node and uses the
JSON_SET
function to set its is_blocked
property.
UPDATEGraphNode
SETproperties=JSON_SET(
properties,
'$.is_blocked',false
)
WHERElabel="account"ANDid=16;
The following example updates a person
node with a new set of properties.
UPDATEGraphNode
SETproperties=JSON'{"name": "David", "age": 43}'
WHERElabel="person"ANDid=4;
The following example uses the
JSON_REMOVE
function to remove the is_blocked
property from an Account
node. After
execution, all other existing properties remain unchanged.
UPDATEGraphNode
SETproperties=JSON_REMOVE(
properties,
'$.is_blocked'
)
WHERElabel="account"ANDid=16;
Delete graph data
The following example deletes the Transfers
edge on Account
nodes that
transferred to blocked accounts.
DELETEFROMGraphEdge
WHERElabel="transfers"ANDidIN{
GRAPHFinGraph
MATCH(a:Account)-[:Transfers]->{1,2}(:Account{is_blocked:TRUE})
RETURNa.id
}
Known limitations
This section lists the limitations of using schemaless data management.
Single table requirement for dynamic labels
You can only have one node table if a dynamic label is used in its definition. This restriction also applies to the edge table. Spanner doesn't permit the following:
- Defining a node table with a dynamic label alongside any other node tables.
- Defining an edge table with a dynamic label alongside any other edge tables.
- Defining multiple node tables or multiple edge tables that each use a dynamic label.
For example, the following code fails when it tries to create multiple graph node with dynamic labels.
CREATEORREPLACEPROPERTYGRAPHFinGraph
NODETABLES(
GraphNodeOne
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties),
GraphNodeTwo
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties),
Account
LABELAccountPROPERTIES(create_time)
)
EDGETABLES(
...
);
Label names must be lowercase
You must store label string values as lowercase for matching. We recommend that you enforce this rule either in the application code or using schema constraints.
While label string values must be stored as lowercase, they aren't case sensitive when you reference them in a query.
The following example shows how to insert labels in lowercase values:
INSERTINTOGraphNode(id,label)VALUES(1,"account");
INSERTINTOGraphNode(id,label)VALUES(2,"account");
You can use case-insensitive labels to match the GraphNode
or GraphEdge
.
GRAPHFinGraph
MATCH(accnt:Account{id:1})-[:Transfers]->(dest_accnt:Account)
RETURNdest_accnt.id;
Property names must be lowercase
You must store property names in lowercase. We recommend that you enforce this rule either in the application code or using schema constraints.
While property names must be stored as lowercase, they aren't case sensitive when you reference them in your query.
The following example inserts the name
and age
properties using lowercase.
INSERTINTOGraphNode(id,label,properties)
VALUES(25,"person",JSON'{"name": "Kim", "age": 27}');
In query text, property names are case insensitive. For example, you can use
either Age
or age
to access the property.
GRAPHFinGraph
MATCH(n:Person{Age:27})
RETURNn.id;
Additional limitations
- Spanner models only top-level keys of
the
JSON
data type as properties. - Property data types must conform to the Spanner JSON type specifications.
Best practices for schemaless data
This section describes best practices that help you model schemaless data.
Define primary keys for nodes and edges
A node's key should be unique across all graph nodes. For example, as an INT64
or string
UUID
column.
If multiple edges exist between two nodes, introduce a unique identifier for the
edge. The schema example uses an application logic INT64
edge_id
column.
When you create the schema for node and edge tables, optionally include the
label
column as a primary key
column if the value
is immutable. If you do this, the composite key formed by all key columns should
be unique across all nodes or edges. This technique improves performance for
queries that are only filtered by label.
For more information about primary key choice, see Choose a primary key.
Create a secondary index for a frequently accessed property
To boost query performance for a property frequently used in filters, create a secondary index against a generated property column. Then, use it in a graph schema and queries.
The following example shows how to add a generated age
column to the
GraphNode
table for a person
node. The value is NULL
for nodes without the
person
label.
ALTERTABLEGraphNode
ADDCOLUMNperson_ageINT64AS
(IF(label="person",LAX_INT64(properties.age),NULL));
The following DDL statement then creates a NULL FILTERED INDEX
for
person_age
and interleaves it into the GraphNode
table for local access.
CREATENULL_FILTEREDINDEXIdxPersonAge
ONGraphNode(id,label,person_age),INTERLEAVEINGraphNode;
The GraphNode
table includes new columns that are available as graph node
properties. To reflect this in the property graph definition, use the CREATE OR
REPLACE PROPERTY GRAPH
statement. This recompiles the definition and includes
the new person_age
column as a property.
For more information, see updating existing node or edge definitions.
The following statement recompiles the definition and includes the new
person_age
column as a property.
CREATEORREPLACEPROPERTYGRAPHFinGraph
NODETABLES(
GraphNode
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
)
EDGETABLES(
GraphEdge
SOURCEKEY(id)REFERENCESGraphNode(id)
DESTINATIONKEY(dest_id)REFERENCESGraphNode(id)
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
);
The following example runs a query with the indexed property.
GRAPHFinGraph
MATCH(person:Person{person_age:43})
RETURNperson.id,person.name;
Optionally, run the ANALYZE
command
after index creation so that the query optimizer is updated with the latest
database statistics.
Use check constraints for data integrity
Spanner supports schema objects such as check constraints to enforce label and property data integrity. This section lists recommendations for check constraints that you can use with schemaless data.
Enforce label values
We recommend that you use NOT NULL
in the label column definition to avoid
undefined label values.
CREATETABLEGraphNode(
idINT64NOTNULL,
labelSTRING(MAX)NOTNULL,
propertiesJSON,
)PRIMARYKEY(id);
Enforce lowercase label values and property names
Because label and property names must be stored as lowercase values, do either of the following:
- Enforce the check in application logic.
- Create check constraints in the schema.
At query time, the label and property name are case insensitive.
The following example shows how to add a node label constraint to the
GraphNode
table to ensure the label is in lowercase.
ALTERTABLEGraphNodeADDCONSTRAINTNodeLabelLowerCaseCheck
CHECK(LOWER(label)=label);
The following example shows how to add a check constraint to the edge property
name. The check uses
JSON_KEYS
to
access the top-level keys.
COALESCE
converts the output to an empty array if JSON_KEYS
returns NULL
and then
checks that each key is lowercase.
ALTERTABLEGraphEdgeADDCONSTRAINTEdgePropertiesLowerCaseCheck
CHECK(NOTarray_includes(COALESCE(JSON_KEYS(properties,1),[]),key->key<>LOWER(key)));
Enforce that properties exist
Create a constraint that checks if a property exists for a label.
In the following example, the constraint checks if a person
node has a name
property.
ALTERTABLEGraphNode
ADDCONSTRAINTNameMustExistForPersonConstraint
CHECK(IF(label='person',properties.nameISNOTNULL,TRUE));
Enforce unique properties
Create property-based constraints that check if the property of a node or edge is unique across nodes or edges with the same label. To do this, use a UNIQUE INDEX against the generated columns of properties.
In the following example, the unique index checks that the name
and country
properties combined are unique for any person
node.
Add a generated column for
PersonName
.ALTERTABLEGraphNode ADDCOLUMNperson_nameSTRING(MAX) AS(IF(label='person',STRING(properties.name),NULL))Hidden;
Add a generated column for
PersonCountry
.ALTERTABLEGraphNode ADDCOLUMNperson_countrySTRING(MAX) AS(IF(label='person',STRING(properties.country),NULL))Hidden;
Create a
NULL_FILTERED
unique index against thePersonName
andPersonCountry
properties.CREATEUNIQUENULL_FILTEREDINDEXNameAndCountryMustBeUniqueForPerson ONGraphNode(person_name,person_country);
Enforce property data type
Enforce a property data type using a data type constraint on a property value
for a label, as shown in the following example. This example uses the
JSON_TYPE
function to check that the name
property of the person
label uses the
STRING
type.
ALTERTABLEGraphNode
ADDCONSTRAINTPersonNameMustBeStringTypeConstraint
CHECK(IF(label='person',JSON_TYPE(properties.name)='string',TRUE));
Combine defined and dynamic labels
Spanner allows nodes in the property graph to have both defined labels (defined in the schema) and dynamic labels (derived from data). Customize labels to use this flexibility.
Consider the following schema that shows the creation of the GraphNode
table:
CREATEORREPLACEPROPERTYGRAPHFinGraph
NODETABLES(
GraphNode
LABELEntity-- Defined label
DYNAMICLABEL(label)-- Dynamic label from data column 'label'
DYNAMICPROPERTIES(properties)
);
Here, every node created from GraphNode
has the defined label Entity
. In
addition, each node has a dynamic label determined by the value in its label
column.
Then, write queries that match nodes based on either label type. For example,
the following query finds nodes using the defined Entity
label:
GRAPHFinGraph
MATCH(node:Entity{id:1})-- Querying by the defined label
RETURNnode.name;
Even though this query uses the defined label Entity
, remember that the
matched node also carries a dynamic label based on its data.
Schema examples
Use the schema examples in this section as templates to create your own schemas. Key schema components include the following:
- Graph input tables creation
- Property graph creation
- Optional: reverse edge traversal index to boost reverse traversal performance
- Optional: label index to boost performance of queries by labels
- Optional: schema constraints to enforce lowercase labels and property names
The following example shows how to create input tables and a property graph:
CREATETABLEGraphNode(
idINT64NOTNULL,
labelSTRING(MAX)NOTNULL,
propertiesJSON
)PRIMARYKEY(id);
CREATETABLEGraphEdge(
idINT64NOTNULL,
dest_idINT64NOTNULL,
edge_idINT64NOTNULL,
labelSTRING(MAX)NOTNULL,
propertiesJSON
)PRIMARYKEY(id,dest_id,edge_id),
INTERLEAVEINPARENTGraphNode;
CREATEPROPERTYGRAPHFinGraph
NODETABLES(
GraphNode
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
)
EDGETABLES(
GraphEdge
SOURCEKEY(id)REFERENCESGraphNode(id)
DESTINATIONKEY(dest_id)REFERENCESGraphNode(id)
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
);
The following example uses an index to improve reverse edge traversal. The
STORING (properties)
clause includes a copy of edge properties, which speeds
up queries that filter on these properties. You can omit the
STORING (properties)
clause if your queries don't benefit from it.
CREATEINDEXR_EDGEONGraphEdge(dest_id,id,edge_id)STORING(properties),
INTERLEAVEINGraphNode;
The following example uses a label index to speed up matching nodes by labels.
CREATEINDEXIDX_NODE_LABELONGraphNode(label);
The following example adds constraints that enforce lowercase labels and
properties. The last two examples use the
JSON_KEYS
function. Optionally, you can enforce the lowercase check in application logic.
ALTERTABLEGraphNodeADDCONSTRAINTnode_label_lower_case
CHECK(LOWER(label)=label);
ALTERTABLEGraphEdgeADDCONSTRAINTedge_label_lower_case
CHECK(LOWER(label)=label);
ALTERTABLEGraphNodeADDCONSTRAINTnode_property_keys_lower_case
CHECK(
NOTarray_includes(COALESCE(JSON_KEYS(properties,1),[]),key->key<>LOWER(key)));
ALTERTABLEGraphEdgeADDCONSTRAINTedge_property_keys_lower_case
CHECK(
NOTarray_includes(COALESCE(JSON_KEYS(properties,1),[]),key->key<>LOWER(key)));
Optimize batch updates of dynamic properties with DML
Modifying dynamic properties using functions like
JSON_SET
and
JSON_REMOVE
involves read-modify-write operations. This can lead to higher cost compared to
updating properties of STRING
or INT64
type.
If workloads involve batch updates to dynamic properties using DML, use the following recommendations to achieve better performance:
Update multiple rows in a single DML statement rather than processing rows individually.
When updating a wide key range, group and sort the affected rows by primary keys. Updating non-overlapping ranges with each DML reduces lock contention.
Use query parameters in DML statements instead of hard coding them to improve performance.
Based on these suggestions, the following example shows how to update the
is_blocked
property for 100 nodes in a single DML statement. The query
parameters include the following:
@node_ids
: Keys ofGraphNode
rows, stored in anARRAY
parameter. If applicable, grouping and sorting them across DMLs achieves better performance.@is_blocked_values
: The corresponding values to be updated, stored in anARRAY
parameter.
UPDATEGraphNode
SETproperties=JSON_SET(
properties,
'$.is_blocked',
CASEid
WHEN@node_ids[OFFSET(0)]THEN@is_blocked_values[OFFSET(0)]
WHEN@node_ids[OFFSET(1)]THEN@is_blocked_values[OFFSET(1)]
...
WHEN@node_ids[OFFSET(99)]THEN@is_blocked_values[OFFSET(99)]
END,
create_if_missing=>TRUE)
WHEREidINUNNEST(@node_ids)
Troubleshoot
This section describes how to troubleshoot issues with schemaless data.
Property appears multiple times in the TO_JSON
result
Issue
The following node models the birthday
and name
properties as dynamic
properties in its JSON
column. Duplicate birthday
and name
properties
appear in the graph element JSON result.
GRAPHFinGraph
MATCH(n:Person{id:14})
RETURNSAFE_TO_JSON(n)ASn;
This returns results similar to the following:
{
...,
"properties":{
"birthday":"1991-12-21 00:00:00",
"name":"Alex",
"id":14,
"label":"person",
"properties":{
"birthday":"1991-12-21 00:00:00",
"name":"Alex"
}
}
...
}
Possible cause
By default, all columns of the base table are defined as properties. Using
TO_JSON
or
SAFE_TO_JSON
to return graph elements results in duplicate properties. This occurs because
the JSON
column (properties
) is a schema-defined property, while the
first-level keys of the JSON
are modeled as dynamic properties.
Recommended solution
To avoid this behavior, use the PROPERTIES ALL COLUMNS EXCEPT
clause to exclude the properties
column when you define properties in the
schema, as shown in the following example:
CREATEORREPLACEPROPERTYGRAPHFinGraph
NODETABLES(
GraphNode
PROPERTIESALLCOLUMNSEXCEPT(properties)
DYNAMICLABEL(label)
DYNAMICPROPERTIES(properties)
);
After the schema change, the returned graph elements of the JSON
data type
don't have duplicates.
GRAPHFinGraph
MATCH(n:Person{id:1})
RETURNTO_JSON(n)ASn;
This query returns the following:
{
...
"properties":{
"birthday":"1991-12-21 00:00:00",
"name":"Alex",
"id":1,
"label":"person",
}
}
Common issues when property values aren't properly converted
To fix the following issues, always use property value conversions when using a property inside a query expression.
Property values comparison without conversion
Issue
No matching signature for operator = for argument types: JSON, STRING
Possible cause
The query doesn't properly convert property values. For example, the name
property
is not converted to STRING
type in comparison:
GRAPHFinGraph
MATCH(p:Person)
WHEREp.name="Alex"
RETURNp.id;
Recommended solution
To fix this issue, use a value conversion before comparison.
GRAPHFinGraph
MATCH(p:Person)
WHERESTRING(p.name)="Alex"
RETURNp.id;
This returns results similar to the following:
+------+
|id|
+------+
|1|
+------+
Alternatively, use a property filter to simplify
equality comparisons where value conversion occurs automatically. Notice that
the value's type ("Alex") must exactly match the property's STRING
type in
JSON
.
GRAPHFinGraph
MATCH(p:Person{name:'Alex'})
RETURNp.id;
This returns results similar to the following:
+------+
|id|
+------+
|1|
+------+
RETURN DISTINCT
property value use without conversion
Issue
Column order_number_str of type JSON cannot be used in `RETURN DISTINCT
Possible cause
In the following example, order_number_str
hasn't been converted before it's
used in the RETURN DISTINCT
statement:
GRAPHFinGraph
MATCH-[t:Transfers]->
RETURNDISTINCTt.order_number_strASorder_number_str;
Recommended solution
To fix this issue, use a value conversion before RETURN DISTINCT
.
GRAPHFinGraph
MATCH-[t:Transfers]->
RETURNDISTINCTSTRING(t.order_number_str)ASorder_number_str;
This returns results similar to the following:
+-----------------+
|order_number_str|
+-----------------+
|302290001255747|
|103650009791820|
|304330008004315|
|304120005529714|
+-----------------+
Property used as a grouping key without conversion
Issue
Grouping by expressions of type JSON is not allowed.
Possible cause
In the following example, t.order_number_str
isn't converted before it's used
to group JSON objects:
GRAPHFinGraph
MATCH(a:Account)-[t:Transfers]->(b:Account)
RETURNt.order_number_str,COUNT(*)AStotal_transfers;
Recommended solution
To fix this issue, use a value conversion before using the property as a grouping key.
GRAPHFinGraph
MATCH(a:Account)-[t:Transfers]->(b:Account)
RETURNSTRING(t.order_number_str)ASorder_number_str,COUNT(*)AStotal_transfers;
This returns results similar to the following:
+-----------------+------------------+
|order_number_str|total_transfers|
+-----------------+------------------+
|302290001255747|1|
|103650009791820|1|
|304330008004315|1|
|304120005529714|2|
+-----------------+------------------+
Property used as an ordering key without conversion
Issue
ORDER BY does not support expressions of type JSON
Possible cause
In the following example, t.amount
isn't converted before it's used for
ordering results:
GRAPHFinGraph
MATCH(a:Account)-[t:Transfers]->(b:Account)
RETURNa.IdASfrom_account,b.IdASto_account,t.amount
ORDERBYt.amountDESC
LIMIT1;
Recommended solution
To fix this issue, do a conversion on t.amount
in the ORDER BY
clause.
GRAPHFinGraph
MATCH(a:Account)-[t:Transfers]->(b:Account)
RETURNa.IdASfrom_account,b.IdASto_account,t.amount
ORDERBYDOUBLE(t.amount)DESC
LIMIT1;
This returns results similar to the following:
+--------------+------------+--------+
|from_account|to_account|amount|
+--------------+------------+--------+
|20|7|500|
+--------------+------------+--------+
Type mismatch during conversion
Issue
The provided JSON input is not an integer
Possible cause
In the following example, the order_number_str
property is stored as a JSON
STRING
data type. If you try to perform a conversion to INT64
, it returns an
error.
GRAPHFinGraph
MATCH-[e:Transfers]->
WHEREINT64(e.order_number_str)=302290001255747
RETURNe.amount;
Recommended solution
To fix this issue, use the exact value converter that matches the value type.
GRAPHFinGraph
MATCH-[e:Transfers]->
WHERESTRING(e.order_number_str)="302290001255747"
RETURNe.amount;
This returns results similar to the following:
+-----------+
|amount|
+-----------+
|JSON"200"|
+-----------+
Alternatively, use a flexible converter when the value is convertible to the target type, as shown in the following example:
GRAPHFinGraph
MATCH-[e:Transfers]->
WHERELAX_INT64(e.order_number_str)=302290001255747
RETURNe.amount;
This returns results similar to the following:
+-----------+
|amount|
+-----------+
|JSON"200"|
+-----------+
What's next
- To learn more about JSON, see Modify JSON data and JSON functions list.
- Compare Spanner Graph and openCypher.
- Migrate to Spanner Graph.