Skip to main content

A Ten-Minute Tour of LoraDB

A guided walkthrough. Run each block in order and you'll end up with a populated graph, the Cypher basics that shape it, and the aggregation patterns you'll reach for most often.

Already installed LoraDB? Good — use whichever binding you picked. The queries are the same in every language. If you haven't, start with Installation.

How-to~10 min

Learn LoraDB in ten minutes

A guided Cypher tour: build a small social graph, then match patterns, filter, project, aggregate, walk variable-length traversals, and mutate the graph.

  1. Create nodes and relationships
    Use CREATE to insert four Person nodes with name and born properties, then connect them with directed FOLLOWS relationships. Labels and relationship types are case-sensitive.
    Jump to section
  2. Match patterns
    Use MATCH to find nodes and traversals. (p:Person) binds every Person to p; (a)-[:FOLLOWS]->(b) walks one hop along a FOLLOWS edge in the given direction.
    Jump to section
  3. Filter with WHERE
    Add WHERE clauses to narrow results: WHERE p.born > 1900, WHERE p.name STARTS WITH 'A', WHERE p.name IN ['Ada', 'Grace']. CASE expressions handle conditional values inline.
    Jump to section
  4. Project and shape results
    Pick the columns you want with RETURN p.name AS name, p.born AS born. Use map projection RETURN p { .* } for full property maps, or compose pipelines with WITH.
    Jump to section
  5. Count and aggregate
    count(*), sum(), avg(), collect() aggregate over the row stream. Non-aggregated columns become the implicit group-by; HAVING-style filtering goes through WITH.
    Jump to section
  6. Walk multi-hop paths
    Variable-length patterns like (a)-[:FOLLOWS*1..3]->(b) traverse one to three hops. shortestPath() and allShortestPaths() find graph-distance answers in a single query.
    Jump to section
  7. Mutate the graph
    SET updates properties, MERGE upserts a pattern with ON CREATE / ON MATCH clauses, DETACH DELETE removes a node and every relationship touching it in one step.
    Jump to section

What you'll learn

StepTopic
1Create nodes and relationships
2MATCH patterns
3WHERE, case, string matching
4RETURN, aliases, map projection
5count, implicit group-by
6Multi-hop and variable-length patterns
7SET, MERGE, DETACH DELETE
8CASE expressions
NextWhere to go from here

Step 1 — Create some data

We'll model a tiny network: a handful of people and who follows whom.

CREATE (ada:Person   {name: 'Ada',   born: 1815})
CREATE (grace:Person {name: 'Grace', born: 1906})
CREATE (alan:Person  {name: 'Alan',  born: 1912})
CREATE (linus:Person {name: 'Linus', born: 1969})

Four Person nodes, each with name and born properties. Person is a label — a tag you can use later to filter MATCH queries. The variables (ada, grace, …) only live for the duration of this query.

Now wire them up:

MATCH (ada:Person   {name: 'Ada'}),
    (grace:Person {name: 'Grace'}),
    (alan:Person  {name: 'Alan'}),
    (linus:Person {name: 'Linus'})
CREATE (grace)-[:FOLLOWS]->(ada),
     (alan)-[:FOLLOWS]->(ada),
     (linus)-[:FOLLOWS]->(alan),
     (linus)-[:FOLLOWS]->(grace)

We re-bind the nodes by name and CREATE four directed FOLLOWS relationships. A relationship is always between two nodes, always has a single type, always has a direction, and can carry its own properties.

Why this matters. In a relational schema you'd model this with a follows join table; in LoraDB the relationship is a first-class value you can pattern-match directly.

Step 2 — Find something

MATCH describes a shape you want to find in the graph. The simplest possible shape: "every Person":

MATCH (p:Person)
RETURN p.name

You'll see four rows back — Ada, Grace, Alan, Linus.

Now a shape with a relationship:

MATCH (follower:Person)-[:FOLLOWS]->(leader:Person)
RETURN follower.name, leader.name

This returns one row per edge — four rows:

followerleader
GraceAda
AlanAda
LinusAlan
LinusGrace

The pattern reads left-to-right: "a :Person named follower who has an outgoing :FOLLOWS relationship to another :Person named leader." MATCH returns every assignment of variables to graph entities that makes the pattern true.

Step 3 — Filter

WHERE narrows the rows. It runs after MATCH and can reference anything the match bound.

MATCH (p:Person)
WHERE p.born >= 1900
RETURN p.name, p.born

Three rows back: Grace (1906), Alan (1912), Linus (1969). WHERE is also where most null-safe checks and string matching live:

MATCH (p:Person)
WHERE p.name STARTS WITH 'A'
RETURN p.name

Two rows: Ada, Alan. Run through string.lower if you want case-insensitive matching:

MATCH (p:Person)
WHERE string.lower(p.name) CONTAINS 'a'
RETURN p.name

Range filters

MATCH (p:Person)
WHERE p.born >= 1900 AND p.born < 1950
RETURN p.name, p.born

LoraDB has no BETWEEN keyword — use explicit >= / <=. See Limitations.

Step 4 — Project and shape results

RETURN can rename with AS, compute expressions, and pick subsets via map projection:

MATCH (p:Person)
RETURN p.name AS name,
     2026 - p.born AS approx_age,
     p {.name, .born} AS record

One row per person; each row has three columns. p {.name, .born} builds a map from the node's properties — useful for shaping results when your consumer only needs a subset.

Step 5 — Count and aggregate

Aggregates collapse rows. The simplest is count(*):

MATCH (p:Person)
RETURN count(*) AS total

One row, one column, value 4. More interestingly: how many people does each person follow?

MATCH (follower:Person)-[:FOLLOWS]->(leader:Person)
RETURN follower.name AS follower,
     count(leader) AS following
ORDER BY following DESC
followerfollowing
Linus2
Grace1
Alan1

Why this works. follower.name is non-aggregated, so it becomes an implicit group key. One row per group. count(leader) is the size of each group.

And the reverse — how many followers does each person have?

MATCH (follower:Person)-[:FOLLOWS]->(leader:Person)
RETURN leader.name AS person,
     count(follower) AS followers
ORDER BY followers DESC

Filter after aggregating (HAVING-style)

Cypher has no HAVING. Use WITH + WHERE:

MATCH (follower:Person)-[:FOLLOWS]->(leader:Person)
WITH leader.name AS person, count(follower) AS followers
WHERE followers >= 2
RETURN person, followers

Step 6 — Walk multi-step patterns

Cypher really earns its keep on multi-hop patterns. Who follows someone who follows Ada?

MATCH (n:Person)-[:FOLLOWS]->(mid:Person)-[:FOLLOWS]->(ada:Person {name: 'Ada'})
RETURN n.name AS two_hops_away

One row: Linus, because Linus → Alan → Ada (and Linus → Grace → Ada).

You can bind the whole path and read its length:

MATCH p = (linus:Person {name: 'Linus'})-[:FOLLOWS*1..3]->(ada:Person {name: 'Ada'})
RETURN path.length(p), path.nodes(p)

*1..3 means "between 1 and 3 hops along :FOLLOWS edges" — see variable-length patterns. path.length(p) is the hop count; path.nodes(p) is the list of nodes on that path.

Shortest path

MATCH p = shortestPath(
(linus:Person {name: 'Linus'})-[:FOLLOWS*]->(ada:Person {name: 'Ada'})
)
RETURN path.length(p) AS hops, [n IN path.nodes(p) | n.name] AS via

Step 7 — Update and delete

SET changes properties or adds labels on existing nodes:

MATCH (ada:Person {name: 'Ada'})
SET ada.field = 'Mathematics'
RETURN ada
MATCH (ada:Person {name: 'Ada'})
SET ada:Pioneer
RETURN labels(ada)
// ['Person', 'Pioneer']

MERGE is "match, or create if missing":

MERGE (p:Person {name: 'Ada'})
ON MATCH  SET p.updated = temporal.timestamp()
ON CREATE SET p.created = temporal.timestamp()
RETURN p.name, p.updated, p.created

And DETACH DELETE removes a node and all its relationships:

MATCH (alan:Person {name: 'Alan'})
DETACH DELETE alan

Step 8 — Conditional values (bonus)

CASE is LoraDB's ternary. Two forms — match a value, or boolean-per-branch:

MATCH (p:Person)
RETURN p.name,
     CASE
       WHEN p.born < 1900  THEN '19th century'
       WHEN p.born < 2000  THEN '20th century'
       ELSE                     '21st century'
     END AS era

Works anywhere a value is allowed — RETURN, WITH, SET, ORDER BY, inside function arguments.

Where to go next

You've now touched every common clause: CREATE, MATCH, WHERE, RETURN, ORDER BY, count, MERGE, SET, DETACH DELETE, and CASE. From here:

  • Query reference — clause-by-clause details.
  • Query examples — copy-paste patterns for common shapes.
  • Aggregation — grouping, collect, percentiles, HAVING-style filtering.
  • Paths — variable-length and shortest paths.
  • Functions — string, math, list, temporal, spatial.
  • Data Types — the complete value catalogue.
  • Limitations — what's intentionally not supported today.

See also