Documentation
6. Querying
Filter, sort, project, and retrieve rows using RelPyDB's chainable query builder.
Query execution order
RelPyDB processes a query as a pipeline. You can chain methods in any order syntactically, but the engine always executes them in this sequence:
- Source rows from the table (or join result)
where— filter rowsorder_by— sort the filtered setselect— project columnsdistinct— deduplicateoffset— skip N rowslimit— take at most N rows
For grouped queries the order is: source → where → group_by → aggregate → having → order_by → select → distinct → offset → limit.
Starting a query
rows = db.query("users").to_list()
db.query(table_name) returns a Query object. Nothing is executed until you call a terminal method like to_list(), first(), or count().
Filtering with where and col
Use col("column_name") to build conditions.
from relpy import RelPy, AutoNumber, col
db = RelPy()
db.create_table("users")
db.add_column("users", "id", AutoNumber, is_primary_key=True)
db.add_column("users", "name", str, nullable=False)
db.add_column("users", "status", str, nullable=False, default="active")
db.add_column("users", "age", int, nullable=True)
db.insert("users", {"name": "Alice", "age": 30})
db.insert("users", {"name": "Bob", "age": 17, "status": "inactive"})
db.insert("users", {"name": "Carol", "age": 25})
# equality
result = db.query("users").where(col("status") == "active").to_list()
Output
| id | name | status | age |
|---|---|---|---|
| 1 | Alice | active | 30 |
| 3 | Carol | active | 25 |
All col operators
| Expression | SQL equivalent | Example |
|---|---|---|
col("x") == v | x = v | col("status") == "active" |
col("x") != v | x != v | col("status") != "inactive" |
col("x") > v | x > v | col("age") > 18 |
col("x") >= v | x >= v | col("age") >= 18 |
col("x") < v | x < v | col("amount") < 100 |
col("x") <= v | x <= v | col("score") <= 99 |
col("x").like(pattern) | x LIKE pattern | col("email").like("%@gmail.com") |
col("x").in_([...]) | x IN (...) | col("status").in_(["paid","pending"]) |
col("x").between(lo, hi) | x BETWEEN lo AND hi | col("amount").between(100, 500) |
col("x").is_null() | x IS NULL | col("deleted_at").is_null() |
col("x").is_not_null() | x IS NOT NULL | col("email").is_not_null() |
Combining conditions
Do not use Python's
and / or. Use &, |, ~ or the AND(), OR(), NOT() functions instead.Operator style
from relpy import col
result = (
db.query("users")
.where(
(col("age") >= 18) &
(col("status") == "active")
)
.to_list()
)
Function style
from relpy import col, AND, OR, NOT
result = (
db.query("users")
.where(
AND(
col("age") >= 18,
col("status") == "active",
)
)
.to_list()
)
Both styles produce the same result. Use whichever is more readable for your team.
select — project columns
RelPyDB
result = (
db.query("users")
.select("id", "name")
.to_list()
)
SQL equivalent
SELECT id, name
FROM users;
Output
| id | name |
|---|---|
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
order_by — sort
# single column
db.query("users").order_by("name").to_list()
# descending
db.query("users").order_by("age", descending=True).to_list()
# multiple columns
db.query("users").order_by(("status", "asc"), ("age", "desc")).to_list()
# alias
db.query("users").orderby("name").to_list()
limit and offset
RelPyDB
db.query("users").limit(10).to_list()
# pagination: page 2, 10 per page
db.query("users").offset(10).limit(10).to_list()
SQL equivalent
SELECT * FROM users LIMIT 10;
SELECT * FROM users
LIMIT 10 OFFSET 10;
distinct
RelPyDB
statuses = (
db.query("users")
.select("status")
.distinct()
.to_list()
)
SQL equivalent
SELECT DISTINCT status
FROM users;
Output
| status |
|---|
| active |
| inactive |
Terminal methods
| Method | Returns | Notes |
|---|---|---|
.to_list() | list[dict] | All matching rows as plain Python dicts. |
.first() | dict | None | First matching row, or None if no rows match. |
.one() | dict | Exactly one row. Raises NoRowsFoundError or MultipleRowsFoundError otherwise. |
.exists() | bool | True if at least one row matches. |
.pluck("col") | list | Flat list of values for a single column. |
.count() | int | Number of matching rows. |
.sum("col") | number | Sum of column values. |
.avg("col") | float | Average of column values. |
.min("col") | value | Minimum value. |
.max("col") | value | Maximum value. |
# practical examples
db.query("users").where(col("status") == "active").count() # 2
db.query("users").pluck("name") # ["Alice", "Bob", "Carol"]
db.query("users").where(col("age").is_not_null()).avg("age") # 24.0
db.query("users").where(col("status") == "active").exists() # True
Full chained example
RelPyDB
result = (
db.query("users")
.where(
(col("age") >= 18) &
col("status").in_(["active", "pending"])
)
.order_by("name")
.select("id", "name", "age")
.limit(5)
.to_list()
)
SQL equivalent
SELECT id, name, age
FROM users
WHERE age >= 18
AND status IN ('active', 'pending')
ORDER BY name
LIMIT 5;