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.

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 toLower if you want case-insensitive matching:

MATCH (p:Person)
WHERE toLower(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 length(p), nodes(p)

*1..3 means "between 1 and 3 hops along :FOLLOWS edges" — see variable-length patterns. length(p) is the hop count; 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 length(p) AS hops, [n IN 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 = timestamp()
ON CREATE SET p.created = 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