Cypher Graph Queries
Zatabase includes a built-in Cypher query engine for graph traversal and pattern matching. Nodes are stored as rows in labeled tables, and relationships (edges) are first-class objects with types, direction, and properties. This lets you model and query graph structures alongside your relational and vector data — all in one database.
Endpoint
Section titled “Endpoint”All Cypher queries are executed via:
POST /v1/cypherAuthorization: Bearer <token>Content-Type: application/json
{"cypher": "YOUR CYPHER QUERY HERE"}Response Format
Section titled “Response Format”{ "rows": [ ... ], "affected": null}rows— Array of JSON objects for read queries (MATCH).affected— Number of nodes/relationships created, deleted, or merged for write operations.
Data Model
Section titled “Data Model”Zatabase’s graph model maps onto its existing table/row storage:
- Nodes are rows in a table. The table name serves as the node label (e.g.,
Person,Movie,City). - Relationships (edges) are directed, typed connections between two nodes. Each edge has a type (e.g.,
KNOWS,LIKES), a direction, and optional properties stored as key-value pairs. - Properties on both nodes and edges can be integers, floats, strings, booleans, vectors, lists, or null.
Property Types
Section titled “Property Types”| Type | Cypher Literal | Example |
|---|---|---|
| Integer | bare number | 42, -7 |
| Float | decimal number | 3.14, 0.5 |
| String | single-quoted | 'hello' |
| Boolean | keyword | true, false |
| Null | keyword | null |
| Vector | bracket array | [0.1, 0.2, 0.3] (in ORDER BY distance) |
MATCH finds patterns in the graph. Every MATCH requires at least one relationship hop.
Basic Pattern Matching
Section titled “Basic Pattern Matching”Find all people who know other people:
MATCH (a:Person)-[r:KNOWS]->(b:Person)RETURN a, b(a:Person)— binds variableato nodes in thePersontable.-[r:KNOWS]->— traverses outgoing edges of typeKNOWS, binding the relationship tor.(b:Person)— binds the target node tob.
curl Example
Section titled “curl Example”curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a, b LIMIT 10"}'Multi-Hop Patterns
Section titled “Multi-Hop Patterns”Chain multiple hops to traverse deeper into the graph:
MATCH (a:Person)-[r:KNOWS]->(b:Person)-[s:LIKES]->(c:Movie)RETURN a, cThis finds people who know someone who likes a movie.
Variable-Length Paths
Section titled “Variable-Length Paths”Use *min..max to match paths of variable length:
MATCH (a:Person)-[:KNOWS*1..3]->(b:Person)WHERE a.name = 'Alice'RETURN bThis finds all people reachable from Alice through 1 to 3 KNOWS hops.
Supported formats:
*1..3— between 1 and 3 hops*2— exactly 2 hops*1..— 1 or more hops (no upper bound)*..3— up to 3 hops
OPTIONAL MATCH
Section titled “OPTIONAL MATCH”OPTIONAL MATCH works like a left outer join: if no matching pattern is found, the unmatched variables are kept as null rather than filtering out the row.
MATCH (p:Person)-[:IN]->(c:City)OPTIONAL MATCH (p:Person)-[:OWNS]->(pet:Pet)RETURN p, petPeople without pets still appear in the results; their pet variable will be unbound.
RETURN
Section titled “RETURN”The RETURN clause specifies what data to include in the results.
Return Variables
Section titled “Return Variables”Return entire nodes:
MATCH (a:Person)-[:KNOWS]->(b:Person)RETURN a, bProperty Access
Section titled “Property Access”Return specific properties:
MATCH (a:Person)-[:KNOWS]->(b:Person)RETURN a.name, b.nameAliases
Section titled “Aliases”Use AS to rename returned properties:
MATCH (a:Person)-[:KNOWS]->(b:Person)RETURN a.name AS source, b.name AS targetEdge Property Access
Section titled “Edge Property Access”Access properties on relationships by binding the edge to a variable:
MATCH (a:Person)-[r:KNOWS]->(b:Person)WHERE r.since > 2020RETURN a.name, b.name, r.sinceAggregations
Section titled “Aggregations”Three aggregation functions are supported:
COUNT — count matching rows:
MATCH (p:Person)-[:IN]->(c:City)RETURN COUNT(*) AS totalMATCH (p:Person)-[:IN]->(c:City)RETURN COUNT(p) AS people_countSUM — sum a numeric property:
MATCH (o:Order)-[:FOR]->(c:Customer)RETURN SUM(o.amount) AS revenueCOLLECT — collect property values into a list:
MATCH (p:Person)-[:IN]->(g:Group)RETURN COLLECT(p.name) AS membersAll aggregation functions support AS aliases.
Filter results with WHERE clauses after the MATCH pattern.
Comparison Operators
Section titled “Comparison Operators”| Operator | Description |
|---|---|
= | Equal to |
<> | Not equal to |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
MATCH (a:Item)-[:HAS]->(t:Tag)WHERE a.price > 20RETURN aCross-type numeric comparisons work (integer vs. float):
WHERE a.price >= 9.99Logical Operators
Section titled “Logical Operators”Combine conditions with AND, OR, and NOT:
MATCH (a:N)-[:R]->(b:M)WHERE a.x = 1 OR a.x = 3RETURN aMATCH (a:N)-[:R]->(b:M)WHERE NOT a.x = 2RETURN aMATCH (a:N)-[:R]->(b:M)WHERE (a.x = 1 OR a.x = 2) AND NOT a.x = 2RETURN aParentheses are supported for grouping.
String Predicates
Section titled “String Predicates”| Predicate | Description |
|---|---|
STARTS WITH | String prefix match |
ENDS WITH | String suffix match |
CONTAINS | Substring match |
MATCH (p:Person)-[:LIVES_IN]->(c:City)WHERE p.name STARTS WITH 'Ali'RETURN pWHERE p.name ENDS WITH 'son'WHERE p.name CONTAINS 'li'NULL Checks
Section titled “NULL Checks”WHERE a.email IS NULLWHERE a.email IS NOT NULLProperty-to-Property Comparison
Section titled “Property-to-Property Comparison”Compare properties across nodes or edges:
MATCH (a:Item)-[:RELATED]->(b:Item)WHERE a.price > b.priceRETURN a, bORDER BY and LIMIT
Section titled “ORDER BY and LIMIT”Restrict the number of returned rows:
MATCH (a:Person)-[:KNOWS]->(b:Person)RETURN a, bLIMIT 10ORDER BY distance()
Section titled “ORDER BY distance()”Order results by vector similarity using the distance() function. This enables graph + vector hybrid queries — a unique Zatabase capability.
MATCH (a:docs)-[:similar]->(b:docs)WHERE a.title = 'Introduction'RETURN bORDER BY distance(b.embedding, [0.1, 0.2, 0.3], 'l2')LIMIT 5The distance() function takes three arguments:
- A property reference to a vector field (e.g.,
b.embedding) - A query vector as a bracket-delimited array
- A distance metric as a string
Supported distance metrics:
| Metric | String | Description |
|---|---|---|
| L2 (Euclidean) | 'l2' | Euclidean distance (default) |
| Cosine | 'cosine' | Cosine distance (1 - cosine similarity) |
| Dot Product | 'dot', 'inner', 'innerproduct' | Negated dot product (for distance ordering) |
This is how you combine graph traversal with vector search — traverse relationships to find related nodes, then rank them by embedding similarity.
CREATE
Section titled “CREATE”Create new nodes and relationships.
Create a Node
Section titled “Create a Node”CREATE (a:Person {name: 'Alice', age: 30})This inserts a row into the Person table with the specified properties. The table is created automatically if it doesn’t exist.
curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "CREATE (a:Person {name: '\''Alice'\'', age: 30})"}'Create Multiple Nodes
Section titled “Create Multiple Nodes”CREATE (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'})Create a Relationship
Section titled “Create a Relationship”Create a relationship between two nodes defined in the same statement:
CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})Relationships can also carry properties:
CREATE (a:Person {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:Person {name: 'Bob'})MERGE is an idempotent create: it finds a matching node or creates one if none exists. This is useful for ensuring data is not duplicated.
MERGE (a:Person {name: 'Alice'})- If a node with label
Personandname = 'Alice'exists, it returns the existing node. - If no match is found, it creates a new node with those properties.
curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MERGE (a:Person {name: '\''Alice'\''})"}'Note: ON MATCH SET / ON CREATE SET clauses are not yet supported. MERGE for relationships requires using a MATCH + CREATE pattern instead.
DELETE
Section titled “DELETE”Delete nodes and relationships found by a MATCH pattern.
Delete a Node
Section titled “Delete a Node”MATCH (a:Person)-[:TAGGED]->(t:Tag)WHERE a.name = 'Alice'DELETE aDETACH DELETE
Section titled “DETACH DELETE”DETACH DELETE removes the node and all its incoming and outgoing edges:
MATCH (a:Person)-[:TAGGED]->(t:Tag)WHERE a.name = 'Alice'DETACH DELETE aDelete a Relationship
Section titled “Delete a Relationship”Delete just the relationship by binding it to a variable:
MATCH (a:Person)-[r:KNOWS]->(b:Person)WHERE a.name = 'Alice' AND b.name = 'Bob'DELETE rDelete Multiple Targets
Section titled “Delete Multiple Targets”MATCH (a:Person)-[r:KNOWS]->(b:Person)WHERE a.name = 'Alice'DELETE a, rPG Wire Protocol Access
Section titled “PG Wire Protocol Access”Cypher queries can also be executed through the PostgreSQL wire protocol using special function syntax. Zatabase registers Cypher-related functions accessible from any PostgreSQL client:
| Function | Description |
|---|---|
cypher_query(query, params) | Execute a Cypher query with parameters |
cypher_match(pattern, where, return) | Execute a MATCH query |
cypher_create(pattern, properties) | Execute a CREATE |
cypher_merge(pattern, properties) | Execute a MERGE |
cypher_delete(match, detach) | Execute a DELETE |
cypher_path(start, end, max_depth) | Find a path between nodes |
cypher_shortest_path(start, end) | Find shortest path |
Graph analysis functions are also available:
| Function | Description |
|---|---|
graph_degree(node, direction) | Get node degree |
graph_neighbors(node, direction) | Get neighboring nodes |
graph_components(type) | Find connected components |
graph_centrality(type, nodes) | Compute centrality metrics |
psql "postgresql://admin:password@your-project.zatabase.io/zatabase" -c \ "SELECT * FROM cypher_query('MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a, b', '{}')"Comparison with Neo4j
Section titled “Comparison with Neo4j”Zatabase’s Cypher implementation covers the core subset of Neo4j’s Cypher query language while adding unique capabilities like vector similarity search within graph traversals.
What’s the Same
Section titled “What’s the Same”| Feature | Zatabase | Neo4j |
|---|---|---|
MATCH with pattern matching | Yes | Yes |
OPTIONAL MATCH | Yes | Yes |
| Node labels and relationship types | Yes | Yes |
Property access (n.name) | Yes | Yes |
WHERE with comparisons | Yes | Yes |
AND / OR / NOT | Yes | Yes |
String predicates (STARTS WITH, ENDS WITH, CONTAINS) | Yes | Yes |
IS NULL / IS NOT NULL | Yes | Yes |
Variable-length paths (*1..3) | Yes | Yes |
CREATE nodes and relationships | Yes | Yes |
MERGE (idempotent create) | Yes | Yes |
DELETE / DETACH DELETE | Yes | Yes |
RETURN with property access and aliases | Yes | Yes |
COUNT, SUM, COLLECT aggregations | Yes | Yes |
ORDER BY and LIMIT | Yes | Yes |
| Edge properties | Yes | Yes |
| Multi-hop patterns | Yes | Yes |
Inline property constraints {name: 'Alice'} | Yes | Yes |
What’s Different
Section titled “What’s Different”| Feature | Zatabase | Neo4j |
|---|---|---|
ORDER BY distance() for vector search | Yes | No |
| Runs alongside SQL and vector search | Yes | No (separate database) |
ON MATCH SET / ON CREATE SET in MERGE | Not yet | Yes |
SET clause for property updates | Not yet | Yes |
WITH clause for query chaining | Not yet | Yes |
UNWIND clause | Not yet | Yes |
GROUP BY | Not yet | Yes |
CASE expressions | Not yet | Yes |
WHERE ... IN [list] | Not yet | Yes |
| Multiple labels per node | Not yet | Yes |
| Index hints | Not yet | Yes |
FOREACH | Not yet | Yes |
CALL procedures | Not yet | Yes |
Path functions (length(), nodes(), relationships()) | Not yet | Yes |
Zatabase-Unique Features
Section titled “Zatabase-Unique Features”- Graph + Vector hybrid queries: Traverse graph patterns then rank results by vector similarity using
ORDER BY distance(). No other graph database offers this natively. - Unified engine: Graph queries, SQL queries, and vector search all operate on the same underlying data — no data duplication or sync required.
- Permission-aware traversal: Graph queries respect Zatabase’s RBAC permission system. Row-level and table-level deny rules are enforced during traversal.
- Persistent edges: Edges are automatically persisted to the ZQL storage engine and survive server restarts.
Complete Examples
Section titled “Complete Examples”Social Network: Find Friends of Friends
Section titled “Social Network: Find Friends of Friends”curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MATCH (me:Person)-[:KNOWS*1..2]->(friend:Person) WHERE me.name = '\''Alice'\'' RETURN friend.name"}'Knowledge Graph: Traverse and Rank by Similarity
Section titled “Knowledge Graph: Traverse and Rank by Similarity”curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MATCH (a:Document)-[:RELATED]->(b:Document) WHERE a.title = '\''Introduction'\'' RETURN b ORDER BY distance(b.embedding, [0.1, 0.2, 0.3, 0.4], '\''cosine'\'') LIMIT 5"}'E-Commerce: Sum Order Values
Section titled “E-Commerce: Sum Order Values”curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MATCH (o:Order)-[:FOR]->(c:Customer) WHERE c.name = '\''Acme'\'' RETURN SUM(o.amount) AS revenue"}'Idempotent Node Creation
Section titled “Idempotent Node Creation”curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MERGE (u:User {email: '\''alice@example.com'\'', name: '\''Alice'\''})"}'Delete with Relationship Cleanup
Section titled “Delete with Relationship Cleanup”curl -s -X POST https://your-project.zatabase.io/v1/cypher \ -H "Authorization: Bearer $ZATABASE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"cypher": "MATCH (a:Person)-[:KNOWS]->(b:Person) WHERE a.name = '\''Alice'\'' DETACH DELETE a"}'Limitations
Section titled “Limitations”- MATCH requires at least one relationship hop. You cannot
MATCH (a:Person) RETURN a— a relationship pattern is required. Use the SQL endpoint for simple table scans. - MERGE for relationships is not supported directly. Use
MATCHto find existing nodes, thenCREATEthe relationship via the API. - ON MATCH SET / ON CREATE SET clauses in MERGE are not yet implemented.
- SET, REMOVE, WITH, UNWIND, FOREACH, CALL are not yet supported.
- Multiple labels per node are not supported. Each node has exactly one label (its table name).
- No CREATE INDEX — indexes are managed through Zatabase’s SQL interface or API.
- Relationship creation in the HTTP handler currently only supports creating nodes via CREATE. Full relationship creation through the Cypher endpoint requires the relationship patterns to reference variables bound in the same CREATE statement.
Best Practices
Section titled “Best Practices”-
Always specify node labels. Unlabeled source nodes require prior variable binding, which is not possible in a single-statement query. Labels also constrain the scan to a single table, improving performance.
-
Use LIMIT on large graphs. Without a limit, MATCH will return all matching patterns, which can be expensive on large datasets. The default server-side cap is 1000 rows.
-
Prefer MERGE for idempotent writes. When ingesting data that may overlap, MERGE prevents duplicate nodes.
-
Use DETACH DELETE for nodes with edges. Plain
DELETEon a node that has edges will remove the node but the dangling edge references are also cleaned up. UseDETACH DELETEfor clarity and to explicitly remove all connected edges. -
Combine graph + vector for semantic search. Zatabase’s unique strength is hybrid queries: traverse a knowledge graph to find candidate nodes, then rank them by embedding similarity using
ORDER BY distance(). -
Use edge properties for weighted graphs. Relationship properties (e.g.,
{weight: 0.8, since: 2020}) are accessible in WHERE and RETURN clauses, enabling weighted graph algorithms. -
Keep variable-length paths bounded. Open-ended paths (
*1..) can be expensive. Set a reasonablemax_hops(e.g.,*1..5) to avoid unbounded exploration.