Query Builder¶
SQLSpec includes a fluent query builder for teams who prefer structured SQL construction.
The builder outputs SQL objects that can be executed with the same driver APIs.
Selects¶
select query¶from sqlspec import SQLSpec, sql
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "builder.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table if not exists teams (id integer primary key, name text)")
session.execute("insert into teams (name) values ('SQLSpec')")
query = sql.select("id", "name").from_("teams").where("name = ?")
result = session.execute(query, "SQLSpec")
print(result.one())
Inserts and Updates¶
insert query¶from sqlspec import SQLSpec, sql
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "builder_insert.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table if not exists users (id integer primary key, name text)")
query = sql.insert("users").columns("name").values("Ada")
result = session.execute(query)
print(result.rows_affected)
update query¶from sqlspec import SQLSpec, sql
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "builder_update.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table if not exists users (id integer primary key, name text)")
session.execute("insert into users (name) values ('Old')")
query = sql.update("users").set("name", "New").where("id = 1")
result = session.execute(query)
print(result.rows_affected)
Upserts (ON CONFLICT)¶
Use .on_conflict() to handle insert conflicts. Chain .do_nothing() to skip
conflicting rows, or .do_update(**columns) to update them.
upsert with on_conflict¶from sqlspec import SQLSpec, sql
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "upsert.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table settings ( key text primary key, value text not null)")
# ON CONFLICT DO NOTHING - skip if key exists
insert_ignore = (
sql.insert("settings").columns("key", "value").values("theme", "dark").on_conflict("key").do_nothing()
)
session.execute(insert_ignore)
# ON CONFLICT DO UPDATE - upsert pattern
upsert = (
sql
.insert("settings")
.columns("key", "value")
.values("theme", "light")
.on_conflict("key")
.do_update(value="light")
)
session.execute(upsert)
result = session.select_one("select value from settings where key = 'theme'")
print(result) # {"value": "light"}
Raw Expressions and RETURNING¶
Use sql.raw() to embed raw SQL fragments (like database functions) inside
builder queries. Use .returning() on INSERT, UPDATE, or DELETE to get back
the affected rows.
raw expressions and returning¶from sqlspec import SQLSpec, sql
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "raw.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute(
"create table events ( id integer primary key, name text, created_at text default (datetime('now')))"
)
session.execute("insert into events (name) values ('signup'), ('login')")
# sql.raw() creates a raw SQL expression for use inside builders
raw_count = sql.raw("COUNT(*)")
query = sql.select("name", raw_count).from_("events").group_by("name")
result = session.execute(query)
print(result.all())
# Use RETURNING clause with INSERT
insert_returning = sql.insert("events").columns("name").values("logout").returning("id", "name")
new_row = session.execute(insert_returning)
print(new_row.one()) # {"id": 3, "name": "logout"}
Joins¶
join query¶from sqlspec import SQLSpec, sql
from sqlspec.adapters.sqlite import SqliteConfig
db_path = tmp_path / "builder_joins.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table if not exists customers (id integer primary key, name text)")
session.execute("create table if not exists orders (id integer primary key, customer_id int)")
session.execute("insert into customers (name) values ('Ada')")
session.execute("insert into orders (customer_id) values (1)")
query = (
sql
.select("orders.id", "customers.name")
.from_("orders")
.join("customers", "orders.customer_id = customers.id")
)
result = session.execute(query)
print(result.all())
Query Modifiers¶
where helpers + pagination¶from sqlspec import SQLSpec
from sqlspec.adapters.sqlite import SqliteConfig
from sqlspec.core import SQL
db_path = tmp_path / "modifiers.db"
spec = SQLSpec()
config = spec.add_config(SqliteConfig(connection_config={"database": str(db_path)}))
with spec.provide_session(config) as session:
session.execute("create table if not exists users (id integer primary key, name text, status text)")
session.execute("insert into users (name, status) values ('Ada', 'active'), ('Bob', 'inactive')")
query = (
SQL("select id, name, status from users")
.where_eq("status", "active")
.select_only("id", "name")
.paginate(page=1, page_size=10)
)
result = session.execute(query)
print(result.all())