Query Builder

SQLSpec query builder demo

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())