AnalyticDB for PostgreSQL supports graph analysis through the age extension, which brings the Cypher query language to your PostgreSQL instance. This page explains how Cypher works and walks through creating a graph, writing data, and running CRUD operations.
Prerequisites
Before you begin, ensure that you have:
An AnalyticDB for PostgreSQL V7.0 instance running minor engine version V7.2.1.0 or later. To check, open the Basic Information page in the AnalyticDB for PostgreSQL console. If the version is too old, update the minor engine version.
The
ageextension installed. On instances running minor engine version 7.2.1.4 or later, the extension is installed automatically. For older minor versions, install the extension manually.ag_catalogadded tosearch_path. Run one of the following:Session-level (current session only):
SET search_path TO "$user", public, ag_catalog;Database-level (permanent):
ALTER DATABASE <database_name> SET search_path TO "$user", public, ag_catalog;
(Optional) If other users need access to the
ag_catalogschema, grant permissions using the initial account or a privileged account with the RDS_SUPERUSER permission:GRANT USAGE ON SCHEMA ag_catalog TO <username>;
Key concepts
What is Cypher?
Cypher is an open source declarative graph query language from the openCypher project. It uses a visual, pattern-based syntax that mirrors the structure of a graph — parentheses for nodes, brackets for relationships, and arrows for direction — making it intuitive for developers already familiar with SQL.
MATCH (n:nodes)-[r:ARE_CONNECTED_TO]->(o:otherNodes)
RETURN n, r, oIn this pattern, (n:nodes) is a node with the label nodes (aliased as n), [r:ARE_CONNECTED_TO] is a directed relationship of type ARE_CONNECTED_TO (aliased as r), and (o:otherNodes) is the target node with the label otherNodes (aliased as o). The query returns all source nodes, target nodes, and their relationships that match this pattern.
Node syntax
Nodes are represented with parentheses ().
| Syntax | Description |
|---|---|
() | Anonymous node — matches any node |
(node) | Node with a variable node for use in queries |
(:City) | Node filtered by the City label |
(city:City) | Node with variable city and label City |
(city:City {name: 'Hangzhou'}) | Node with a property filter — equivalent to a WHERE clause |
(city:City {name: 'Hangzhou', province: 'Zhejiang'}) | Node with multiple property filters |
Relationship syntax
Relationships are represented with hyphens --. Add square brackets [] to include a variable, type, or properties. Add > for direction.
| Syntax | Description |
|---|---|
-- | Anonymous, undirected relationship |
--> | Anonymous, directed relationship |
-[role]-> | Directed relationship with variable role |
-[:ACTED_IN]-> | Directed relationship with type ACTED_IN |
-[role:ACTED_IN]-> | Directed relationship with variable and type |
-[role:ACTED_IN {roles: ['Neo']}]-> | Directed relationship with variable, type, and property |
Pattern syntax
Combine node and relationship syntax to express rich graph patterns. For example:
(city_from:City {province: 'Zhejiang'})-[:NEXT_TO]-(city_next:City {province: 'Jiangsu'})This pattern matches pairs of adjacent cities where one is in Zhejiang province and the other is in Jiangsu province, connected by an undirected NEXT_TO relationship.
Example graph
All examples in this page use the following graph. The graph contains three Person nodes (Zhang San, Li Si, and Wang Wu) and two Company nodes (Company A and Company C). Persons are connected to companies with EMPLOYED edges, and persons are connected to each other with FRIENDS and TEACHER edges.
Run the following to create the graph and load the example data:
-- Create the graph
SELECT create_graph('relation');
-- Load example data
SELECT * FROM cypher('relation', $$
CREATE (ZhangSan:Person {name: 'Zhang San'})
CREATE (LiSi:Person {name: 'Li Si'})
CREATE (WangWu:Person {name: 'Wang Wu'})
CREATE (company_a:Company {name: 'Company A'})
CREATE (company_c:Company {name: 'Company C'})
CREATE (ZhangSan)-[:EMPLOYED]->(company_a)
CREATE (LiSi)-[:EMPLOYED]->(company_c)
CREATE (WangWu)-[:EMPLOYED]->(company_a)
CREATE (ZhangSan)-[:FRIENDS]->(LiSi)
CREATE (WangWu)-[:TEACHER]->(ZhangSan)
$$) AS (result_a agtype);The following sections show how to query, update, and delete data in this graph.
Create a graph
Use the create_graph function to create a new graph.
SELECT create_graph('<graph_name>');Replace <graph_name> with the name of the graph.
Write data
All Cypher write operations go through the cypher function in ag_catalog. Use CREATE statements inside the cypher call to add nodes and relationships.
SELECT * FROM cypher('<graph_name>', $$
/* Write a Cypher expression. */
$$) AS (result_a agtype);The cypher function returns results as agtype. Each returned row contains the matched vertex or edge as a JSON-like object annotated with ::vertex or ::edge.
Query data
Use MATCH and RETURN to query graph data. MATCH specifies the graph pattern to find; RETURN specifies what to include in the results. Add a WHERE clause to filter matches.
SELECT * FROM cypher('<graph_name>', $$
MATCH <pattern>
WHERE <condition> -- optional
RETURN <variables>
$$) AS (result_a agtype);Query all nodes or edges
Find all nodes with the Person label:
SELECT * FROM cypher('relation', $$
MATCH (m:Person)
RETURN m
$$) AS (result_a agtype);Result:
| result_a |
|---|
{"id": 844424930131969, "label": "Person", "properties": {"name": "Zhang San"}}::vertex |
{"id": 844424930131970, "label": "Person", "properties": {"name": "Li Si"}}::vertex |
{"id": 844424930131971, "label": "Person", "properties": {"name": "Wang Wu"}}::vertex |
Find all edges with the EMPLOYED label:
SELECT * FROM cypher('relation', $$
MATCH (:Person)-[r:EMPLOYED]->(:Company)
RETURN r
$$) AS (result_a agtype);Result:
| result_a |
|---|
{"id": 1407374883553281, "label": "EMPLOYED", "end_id": 1125899906842625, "start_id": 844424930131969, "properties": {}}::edge |
{"id": 1407374883553282, "label": "EMPLOYED", "end_id": 1125899906842626, "start_id": 844424930131970, "properties": {}}::edge |
{"id": 1407374883553283, "label": "EMPLOYED", "end_id": 1125899906842625, "start_id": 844424930131971, "properties": {}}::edge |
Filter results with WHERE
Add a WHERE clause to narrow results to specific nodes or edges.
Find all employees of Company A:
SELECT * FROM cypher('relation', $$
MATCH (p:Person)-[:EMPLOYED]->(a:Company)
WHERE a.name = 'Company A'
RETURN p
$$) AS (result_a agtype);Result:
| result_a |
|---|
{"id": 844424930131969, "label": "Person", "properties": {"name": "Zhang San"}}::vertex |
{"id": 844424930131971, "label": "Person", "properties": {"name": "Wang Wu"}}::vertex |
Find Wang Wu's students:
SELECT * FROM cypher('relation', $$
MATCH (p1:Person)-[:TEACHER]->(p2:Person)
WHERE p1.name = 'Wang Wu'
RETURN p2
$$) AS (result_a agtype);Result:
| result_a |
|---|
{"id": 844424930131969, "label": "Person", "properties": {"name": "Zhang San"}}::vertex |
Update data
Use MATCH to locate a node or edge, then use SET to add or update its properties.
SELECT * FROM cypher('<graph_name>', $$
MATCH <pattern>
SET <property>
RETURN <variable>
$$) AS (result_a agtype);Update Zhang San's name to Zhang 3:
SELECT * FROM cypher('relation', $$
MATCH (p:Person {name: 'Zhang San'})
SET p.name = 'Zhang 3'
RETURN p
$$) AS (result_a agtype);Result:
| result_a |
|---|
{"id": 844424930131969, "label": "Person", "properties": {"name": "Zhang 3"}}::vertex |
Add a gender property to the updated node:
SELECT * FROM cypher('relation', $$
MATCH (p:Person {name: 'Zhang 3'})
SET p.gender = 'male'
RETURN p
$$) AS (result_a agtype);Result:
| result_a |
|---|
{"id": 844424930131969, "label": "Person", "properties": {"name": "Zhang 3", "gender": "male"}}::vertex |
Delete data
Remove a property
Use REMOVE to delete a property from a node or edge.
SELECT * FROM cypher('<graph_name>', $$
MATCH <pattern>
REMOVE <property>
$$) AS (result_a agtype);Remove the gender property from Zhang San:
SELECT * FROM cypher('relation', $$
MATCH (p:Person {name: 'Zhang 3'})
REMOVE p.gender
RETURN p
$$) AS (result_a agtype);Delete a node
Use DELETE to delete a node that has no edges.
SELECT * FROM cypher('<graph_name>', $$
MATCH <pattern>
DELETE <variable>
$$) AS (result_a agtype);Create and then delete a node named Zhao Liu:
-- Create the node.
SELECT * FROM cypher('relation', $$
CREATE (:Person {name: 'Zhao Liu'})
$$) AS (result_a agtype);
-- Delete the node.
SELECT * FROM cypher('relation', $$
MATCH (p:Person {name: 'Zhao Liu'})
DELETE p
$$) AS (result_a agtype);DELETE fails if the node has edges. Use DETACH DELETE instead to remove the node and all its edges in one step.
Delete an edge
Match the edge by specifying both endpoints in WHERE, then delete it with DELETE.
Create Zhao Liu and an EMPLOYED edge to Company C, then delete the edge:
-- Create the node and edge.
SELECT * FROM cypher('relation', $$
CREATE (ZhaoLiu:Person {name: 'Zhao Liu'})
CREATE (ZhaoLiu)-[:EMPLOYED]->(:Company {name: 'Company C'})
$$) AS (result_a agtype);
-- Delete the edge.
SELECT * FROM cypher('relation', $$
MATCH (p:Person)-[r:EMPLOYED]->(c:Company)
WHERE p.name = 'Zhao Liu' AND c.name = 'Company C'
DELETE r
$$) AS (result_a agtype);Delete a node and its edges
When a node has associated edges, use DETACH DELETE to remove the node and all its edges.
SELECT * FROM cypher('<graph_name>', $$
MATCH <pattern>
DETACH DELETE <variable>
$$) AS (result_a agtype);Create Zhao Liu with an EMPLOYED edge, then delete both:
-- Create the node and edge.
SELECT * FROM cypher('relation', $$
CREATE (ZhaoLiu:Person {name: 'Zhao Liu'})
CREATE (ZhaoLiu)-[:EMPLOYED]->(:Company {name: 'Company C'})
$$) AS (result_a agtype);
-- Delete the node and its edges.
SELECT * FROM cypher('relation', $$
MATCH (p:Person {name: 'Zhao Liu'})
DETACH DELETE p
$$) AS (result_a agtype);