Getting Started with SQL
This content is for v0.12.0. Switch to the latest version for up-to-date documentation.
This tutorial shows how to use GraphQLite directly from the SQLite command-line interface. No Python or Rust required — just sqlite3 and the compiled extension.
What You Will Learn
Section titled “What You Will Learn”- Build the extension from source
- Load it into the SQLite CLI
- Create nodes and relationships with Cypher
- Query patterns with MATCH and WHERE
- Use parameterized queries
- Run a graph algorithm and read the results
Prerequisites
Section titled “Prerequisites”- SQLite 3.x CLI (
sqlite3 --version) - A C compiler, Bison, and Flex (for building from source), or a pre-built binary from the Python wheel
Step 1: Get the Extension
Section titled “Step 1: Get the Extension”Option A — Build from source
Section titled “Option A — Build from source”# Clone the repositorygit clone https://github.com/colliery-io/graphqlite.gitcd graphqlite
# macOSbrew install bison flex sqliteexport PATH="$(brew --prefix bison)/bin:$PATH"make extension RELEASE=1
# Linux (Debian/Ubuntu)sudo apt-get install build-essential bison flex libsqlite3-devmake extension RELEASE=1The compiled extension lands in:
build/graphqlite.dylib(macOS)build/graphqlite.so(Linux)build/graphqlite.dll(Windows)
Option B — Extract from the Python package
Section titled “Option B — Extract from the Python package”pip install graphqlitepython -c "import graphqlite; print(graphqlite.loadable_path())"# /path/to/site-packages/graphqlite/graphqlite.dylibUse that path anywhere this tutorial says build/graphqlite.
Step 2: Open SQLite and Load the Extension
Section titled “Step 2: Open SQLite and Load the Extension”sqlite3 social.dbInside the SQLite prompt:
-- Load the extension (adjust extension for your platform).load build/graphqlite
-- Confirm it loadedSELECT cypher('RETURN 1 + 1 AS result');-- [{"result":2}]Enable column headers for readable output:
.mode column.headers onStep 3: Create Nodes
Section titled “Step 3: Create Nodes”Create a small social network. Each CREATE call returns an empty JSON array [] — that is normal for write operations.
SELECT cypher('CREATE (a:Person {name: "Alice", age: 30, city: "London"})');SELECT cypher('CREATE (b:Person {name: "Bob", age: 25, city: "Paris"})');SELECT cypher('CREATE (c:Person {name: "Carol", age: 35, city: "London"})');SELECT cypher('CREATE (d:Person {name: "Dave", age: 28, city: "Berlin"})');Verify the nodes exist:
SELECT cypher('MATCH (p:Person) RETURN p.name, p.age, p.city ORDER BY p.name');Output:
[{"p.name":"Alice","p.age":30,"p.city":"London"}, {"p.name":"Bob","p.age":25,"p.city":"Paris"}, {"p.name":"Carol","p.age":35,"p.city":"London"}, {"p.name":"Dave","p.age":28,"p.city":"Berlin"}]Use json_each() to get one row per result:
SELECT json_extract(value, '$.p.name') AS name, json_extract(value, '$.p.age') AS age, json_extract(value, '$.p.city') AS cityFROM json_each(cypher('MATCH (p:Person) RETURN p.name, p.age, p.city ORDER BY p.name'));Output:
name age city----- --- ------Alice 30 LondonBob 25 ParisCarol 35 LondonDave 28 BerlinStep 4: Create Relationships
Section titled “Step 4: Create Relationships”-- Alice knows BobSELECT cypher(' MATCH (a:Person {name: "Alice"}), (b:Person {name: "Bob"}) CREATE (a)-[:KNOWS {since: 2020}]->(b)');
-- Alice knows CarolSELECT cypher(' MATCH (a:Person {name: "Alice"}), (c:Person {name: "Carol"}) CREATE (a)-[:KNOWS {since: 2018}]->(c)');
-- Bob knows DaveSELECT cypher(' MATCH (b:Person {name: "Bob"}), (d:Person {name: "Dave"}) CREATE (b)-[:KNOWS {since: 2022}]->(d)');
-- Carol knows DaveSELECT cypher(' MATCH (c:Person {name: "Carol"}), (d:Person {name: "Dave"}) CREATE (c)-[:KNOWS {since: 2021}]->(d)');Step 5: Query Patterns with MATCH and WHERE
Section titled “Step 5: Query Patterns with MATCH and WHERE”Who does Alice know?
Section titled “Who does Alice know?”SELECT json_extract(value, '$.friend.name') AS friend, json_extract(value, '$.r.since') AS sinceFROM json_each(cypher(' MATCH (a:Person {name: "Alice"})-[r:KNOWS]->(friend) RETURN friend, r'));Output:
friend since------ -----Bob 2020Carol 2018People in a specific city
Section titled “People in a specific city”SELECT json_extract(value, '$.name') AS nameFROM json_each(cypher(' MATCH (p:Person) WHERE p.city = "London" RETURN p.name AS name'));Output:
name-----AliceCarolFriends of friends (two hops)
Section titled “Friends of friends (two hops)”Who can Alice reach through two KNOWS relationships?
SELECT json_extract(value, '$.fof') AS friend_of_friendFROM json_each(cypher(' MATCH (a:Person {name: "Alice"})-[:KNOWS]->()-[:KNOWS]->(fof) RETURN DISTINCT fof.name AS fof'));Output:
friend_of_friend----------------DaveFilter with WHERE and aggregation
Section titled “Filter with WHERE and aggregation”People who know more than one person:
SELECT json_extract(value, '$.name') AS person, json_extract(value, '$.count') AS knows_countFROM json_each(cypher(' MATCH (a:Person)-[:KNOWS]->(b) WITH a.name AS name, count(b) AS count WHERE count > 1 RETURN name, count ORDER BY count DESC'));Output:
person knows_count------ -----------Alice 2Step 6: Use Parameterized Queries
Section titled “Step 6: Use Parameterized Queries”For any value that comes from outside the query — user input, a variable, application data — use the $param syntax with a JSON parameters string as the second argument to cypher():
-- Find a person by name using a parameterSELECT cypher( 'MATCH (p:Person {name: $name}) RETURN p.name, p.age, p.city', '{"name": "Carol"}');-- [{"p.name":"Carol","p.age":35,"p.city":"London"}]Multiple parameters:
SELECT cypher( 'MATCH (p:Person) WHERE p.age >= $min_age AND p.city = $city RETURN p.name, p.age', '{"min_age": 28, "city": "London"}');-- [{"p.name":"Alice","p.age":30},{"p.name":"Carol","p.age":35}]Parameters protect against injection and correctly handle special characters in string values.
Step 7: Run PageRank
Section titled “Step 7: Run PageRank”GraphQLite includes 15+ graph algorithms. Load the graph cache first, then call the algorithm function inside a RETURN clause.
-- Load the graph into the algorithm cacheSELECT cypher('RETURN gql_load_graph()');Run PageRank and display the results as a table:
SELECT json_extract(value, '$.user_id') AS person, printf('%.4f', json_extract(value, '$.score')) AS pagerankFROM json_each(cypher('RETURN pageRank(0.85, 20)'), '$[0].column_0')ORDER BY json_extract(value, '$.score') DESC;Output:
person pagerank------ --------carol 0.2000dave 0.1800bob 0.1600alice 0.1200Dave and Carol score highest because they have multiple incoming paths from well-connected nodes.
Complete Script
Section titled “Complete Script”Save the following as social.sql and run it with sqlite3 < social.sql:
.load build/graphqlite.mode column.headers on
-- NodesSELECT cypher('CREATE (a:Person {name: "Alice", age: 30, city: "London"})');SELECT cypher('CREATE (b:Person {name: "Bob", age: 25, city: "Paris"})');SELECT cypher('CREATE (c:Person {name: "Carol", age: 35, city: "London"})');SELECT cypher('CREATE (d:Person {name: "Dave", age: 28, city: "Berlin"})');
-- RelationshipsSELECT cypher('MATCH (a:Person {name: "Alice"}), (b:Person {name: "Bob"}) CREATE (a)-[:KNOWS {since: 2020}]->(b)');SELECT cypher('MATCH (a:Person {name: "Alice"}), (c:Person {name: "Carol"}) CREATE (a)-[:KNOWS {since: 2018}]->(c)');SELECT cypher('MATCH (b:Person {name: "Bob"}), (d:Person {name: "Dave"}) CREATE (b)-[:KNOWS {since: 2022}]->(d)');SELECT cypher('MATCH (c:Person {name: "Carol"}), (d:Person {name: "Dave"}) CREATE (c)-[:KNOWS {since: 2021}]->(d)');
SELECT '--- All people ---';SELECT json_extract(value, '$.p.name') AS name, json_extract(value, '$.p.age') AS ageFROM json_each(cypher('MATCH (p:Person) RETURN p.name, p.age ORDER BY p.name'));
SELECT '--- Who Alice knows ---';SELECT json_extract(value, '$.friend') AS friendFROM json_each(cypher('MATCH (:Person {name: "Alice"})-[:KNOWS]->(f) RETURN f.name AS friend'));
SELECT '--- PageRank ---';SELECT cypher('RETURN gql_load_graph()');SELECT json_extract(value, '$.user_id') AS person, printf('%.4f', json_extract(value, '$.score')) AS scoreFROM json_each(cypher('RETURN pageRank(0.85, 20)'), '$[0].column_0')ORDER BY json_extract(value, '$.score') DESC;Next Steps
Section titled “Next Steps”- Query Patterns (SQL) — Variable-length paths, OPTIONAL MATCH, WITH, UNWIND, CASE, UNION
- Graph Algorithms (SQL) — All 15+ algorithms with SQL extraction patterns
- SQL Interface Reference — Complete
cypher()function documentation and schema tables