Builder¶
The builder provides a fluent API for composing SQL statements with chaining, filters, and dialect-aware SQL compilation.
Example¶
builder usage¶from sqlspec import sql
query = sql.select("id", "name").from_("users").where_eq("status", "active").limit(10).offset(0)
Factory¶
- class sqlspec.builder.SQLFactory[source]¶
Bases:
objectFactory for creating SQL builders and column expressions.
- __call__(statement, dialect=None)[source]¶
Create a SelectBuilder from a SQL string, or SQL object for DML with RETURNING.
- Parameters:
- Return type:
typing.Any
- Returns:
SelectBuilder instance for SELECT/WITH statements, SQL object for DML statements with RETURNING clause.
- Raises:
SQLBuilderError – If the SQL is not a SELECT/CTE/DML+RETURNING statement.
- explain(statement, *, analyze=False, verbose=False, format=None, dialect=None)[source]¶
Create an EXPLAIN builder for a SQL statement.
Wraps any SQL statement in an EXPLAIN clause with dialect-aware syntax generation.
- Parameters:
statement¶ (
str|Expression|SQL|SQLBuilderProtocol) – SQL statement to explain (string, expression, SQL object, or builder)analyze¶ (
bool) – Execute the statement and show actual runtime statisticsformat¶ (
ExplainFormat|str|None) – Output format (TEXT, JSON, XML, YAML, TREE, TRADITIONAL)dialect¶ (
Union[str,Dialect,Type[Dialect],None]) – Optional SQL dialect override
- Return type:
Explain- Returns:
Explain builder for further configuration
Examples
- Basic EXPLAIN:
plan = sql.explain(“SELECT * FROM users”).build()
- With options:
- plan = (
sql.explain(“SELECT * FROM users”, analyze=True, format=”json”) .buffers() .timing() .build()
)
- From QueryBuilder:
query = sql.select(“*”).from_(“users”).where(“id = :id”, id=1) plan = sql.explain(query, analyze=True).build()
- Chained configuration:
- plan = (
sql.explain(sql.select(“*”).from_(“large_table”)) .analyze() .format(“json”) .buffers() .timing() .build()
)
- property merge_: Merge¶
Create a new MERGE builder (property shorthand).
Property that returns a new Merge builder instance using the factory’s default dialect. Cleaner syntax alternative to merge() method.
Examples
query = sql.merge_.into(“products”).using(data, alias=”src”) query = sql.merge_.into(“products”, alias=”t”).on(“t.id = src.id”)
- Returns:
New Merge builder instance
- upsert(table, dialect=None)[source]¶
Create an upsert builder (MERGE or INSERT ON CONFLICT).
Automatically selects the appropriate builder based on database dialect: - PostgreSQL 15+, Oracle, BigQuery: Returns MERGE builder - SQLite, DuckDB, MySQL: Returns INSERT builder with ON CONFLICT support
- Parameters:
- Return type:
Merge|Insert- Returns:
MERGE builder for supported databases, INSERT builder otherwise
Examples
- PostgreSQL/Oracle/BigQuery (uses MERGE):
- upsert_query = (
sql.upsert(“products”, dialect=”postgres”) .using([{“id”: 1, “name”: “Product 1”}], alias=”src”) .on(“t.id = src.id”) .when_matched_then_update(name=”src.name”) .when_not_matched_then_insert(id=”src.id”, name=”src.name”)
)
- SQLite/DuckDB/MySQL (uses INSERT ON CONFLICT):
- upsert_query = (
sql.upsert(“products”, dialect=”sqlite”) .values(id=1, name=”Product 1”) .on_conflict(“id”) .do_update(name=”EXCLUDED.name”)
)
- create_materialized_view(view_name, dialect=None)[source]¶
Create a CREATE MATERIALIZED VIEW builder.
- copy_from(table, source, *, columns=None, options=None, dialect=None)[source]¶
Build a COPY … FROM statement.
- Return type:
- copy_to(table, target, *, columns=None, options=None, dialect=None)[source]¶
Build a COPY … TO statement.
- Return type:
- copy(table, *, source=None, target=None, columns=None, options=None, dialect=None)[source]¶
Build a COPY statement, inferring direction from provided arguments.
- Return type:
- property case_: Case¶
Create a CASE expression builder.
- Returns:
Case builder instance for CASE expression building.
- property row_number_: WindowFunctionBuilder¶
Create a ROW_NUMBER() window function builder.
- property rank_: WindowFunctionBuilder¶
Create a RANK() window function builder.
- property dense_rank_: WindowFunctionBuilder¶
Create a DENSE_RANK() window function builder.
- property lag_: WindowFunctionBuilder¶
Create a LAG() window function builder.
- property lead_: WindowFunctionBuilder¶
Create a LEAD() window function builder.
- property count_over_: WindowFunctionBuilder¶
Create a COUNT(*) OVER() window function builder.
Returns a WindowFunctionBuilder pre-configured with COUNT(*) for fluent chaining. Useful for pagination queries where you want to get the total count in the same query.
- Returns:
WindowFunctionBuilder configured for COUNT(*) OVER()
Example
>>> query = ( ... sql ... .select("*", sql.count_over_.as_("total")) ... .from_("users") ... .where_eq("status", "active") ... .limit(10) ... ) # Produces: SELECT *, COUNT(*) OVER() AS total FROM users WHERE status = :status LIMIT 10
>>> # With partition: >>> query = sql.select( ... "*", ... sql.count_over_.partition_by("department").as_( ... "dept_count" ... ), ... )
- property sum_over_: WindowFunctionBuilder¶
Create a SUM() OVER() window function builder.
- property avg_over_: WindowFunctionBuilder¶
Create an AVG() OVER() window function builder.
- property max_over_: WindowFunctionBuilder¶
Create a MAX() OVER() window function builder.
- property min_over_: WindowFunctionBuilder¶
Create a MIN() OVER() window function builder.
- property exists_: SubqueryBuilder¶
Create an EXISTS subquery builder.
- property in_: SubqueryBuilder¶
Create an IN subquery builder.
- property any_: SubqueryBuilder¶
Create an ANY subquery builder.
- property all_: SubqueryBuilder¶
Create an ALL subquery builder.
- property inner_join_: JoinBuilder¶
Create an INNER JOIN builder.
- property left_join_: JoinBuilder¶
Create a LEFT JOIN builder.
- property right_join_: JoinBuilder¶
Create a RIGHT JOIN builder.
- property full_join_: JoinBuilder¶
Create a FULL OUTER JOIN builder.
- property cross_join_: JoinBuilder¶
Create a CROSS JOIN builder.
- property lateral_join_: JoinBuilder¶
Create a LATERAL JOIN builder.
- Returns:
JoinBuilder configured for LATERAL JOIN
- property left_lateral_join_: JoinBuilder¶
Create a LEFT LATERAL JOIN builder.
- Returns:
JoinBuilder configured for LEFT LATERAL JOIN
- property cross_lateral_join_: JoinBuilder¶
Create a CROSS LATERAL JOIN builder.
- Returns:
JoinBuilder configured for CROSS LATERAL JOIN
- __getattr__(name)[source]¶
Dynamically create column references.
- Parameters:
- Return type:
Column- Returns:
Column object for the given name.
Note
Special SQL constructs like case_, row_number_, etc. are handled as properties for type safety.
- static raw(sql_fragment, **parameters)[source]¶
Create a raw SQL expression from a string fragment with optional parameters.
- Parameters:
- Return type:
Expression|SQL- Returns:
SQLGlot expression from the parsed SQL fragment (if no parameters). SQL statement object (if parameters provided).
- Raises:
SQLBuilderError – If the SQL fragment cannot be parsed.
Example
```python expr = sql.raw(“COALESCE(name, ‘Unknown’)”)
- stmt = sql.raw(
“LOWER(name) LIKE LOWER(:pattern)”, pattern=f”%{query}%”
)
- expr = sql.raw(
“price BETWEEN :min_price AND :max_price”, min_price=100, max_price=500,
)
- query = sql.select(
“name”, sql.raw(
“ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC)”
),
- count_over(column='*', partition_by=None)[source]¶
Create a COUNT() OVER() window function for inline total counts.
This is particularly useful for pagination queries where you want to get the total count in the same query as the paginated results.
- Parameters:
- Return type:
FunctionExpression- Returns:
COUNT() OVER() window function expression.
Example
>>> # Simple total count alongside paginated results: >>> query = ( ... sql ... .select("id", "name", sql.count_over().as_("total")) ... .from_("users") ... .where_eq("status", "active") ... .limit(10) ... .offset(0) ... ) # Produces: SELECT id, name, COUNT(*) OVER() AS total FROM users WHERE status = :status LIMIT 10
>>> # With partition (count per group): >>> query = sql.select( ... "*", ... sql.count_over(partition_by="department").as_( ... "dept_count" ... ), ... )
- sum_over(column, partition_by=None, order_by=None)[source]¶
Create a SUM() OVER() window function.
- Parameters:
- Return type:
FunctionExpression- Returns:
SUM() OVER() window function expression.
- avg_over(column, partition_by=None, order_by=None)[source]¶
Create an AVG() OVER() window function.
- Parameters:
- Return type:
FunctionExpression- Returns:
AVG() OVER() window function expression.
- max_over(column, partition_by=None, order_by=None)[source]¶
Create a MAX() OVER() window function.
- Parameters:
- Return type:
FunctionExpression- Returns:
MAX() OVER() window function expression.
- min_over(column, partition_by=None, order_by=None)[source]¶
Create a MIN() OVER() window function.
- Parameters:
- Return type:
FunctionExpression- Returns:
MIN() OVER() window function expression.
- static to_literal(value)[source]¶
Convert a Python value to a SQLGlot literal expression.
Uses SQLGlot’s built-in exp.convert() function for literal creation. Handles all Python primitive types: - None -> exp.Null (renders as NULL) - bool -> exp.Boolean (renders as TRUE/FALSE or 1/0 based on dialect) - int/float -> exp.Literal with is_number=True - str -> exp.Literal with is_string=True - exp.Expression -> returned as-is (passthrough)
- static decode(column, *args)[source]¶
Create a DECODE expression (Oracle-style conditional logic).
DECODE compares column to each search value and returns the corresponding result. If no match is found, returns the default value (if provided) or NULL.
- Parameters:
- Raises:
ValueError – If fewer than two search/result pairs are provided.
- Return type:
FunctionExpression- Returns:
CASE expression equivalent to DECODE.
- static nvl(column, substitute_value)[source]¶
Create an NVL (Oracle-style) expression using COALESCE.
- static nvl2(column, value_if_not_null, value_if_null)[source]¶
Create an NVL2 (Oracle-style) expression using CASE.
NVL2 returns value_if_not_null if column is not NULL, otherwise returns value_if_null.
- Parameters:
- Return type:
ConversionExpression- Returns:
CASE expression equivalent to NVL2.
Example
`python sql.nvl2("salary", "Has Salary", "No Salary") `
- static bulk_insert(table_name, column_count, placeholder_style='?')[source]¶
Create bulk INSERT expression for executemany operations.
For bulk loading operations like CSV ingestion where an INSERT expression with placeholders for executemany() is needed.
- Parameters:
- Return type:
FunctionExpression- Returns:
INSERT expression with placeholders for bulk operations
- static case()[source]¶
Create a CASE expression builder.
- Return type:
Case- Returns:
CaseExpressionBuilder for building CASE expressions.
- lag(column, offset=1, default=None, partition_by=None, order_by=None)[source]¶
Create a LAG() window function.
LAG accesses data from a previous row in the same result set without using a self-join.
- Parameters:
- Return type:
FunctionExpression- Returns:
LAG window function expression.
Example
>>> sql.lag("salary", offset=1, order_by="hire_date") >>> sql.lag("price", partition_by="category", order_by="date")
- lead(column, offset=1, default=None, partition_by=None, order_by=None)[source]¶
Create a LEAD() window function.
LEAD accesses data from a subsequent row in the same result set without using a self-join.
- Parameters:
- Return type:
FunctionExpression- Returns:
LEAD window function expression.
Example
>>> sql.lead("salary", offset=1, order_by="hire_date") >>> sql.lead("price", partition_by="category", order_by="date")
Select Builder¶
- class sqlspec.builder.Select[source]¶
Bases:
QueryBuilder,WhereClauseMixin,OrderByClauseMixin,LimitOffsetClauseMixin,SelectClauseMixin,JoinClauseMixin,HavingClauseMixin,SetOperationMixin,CommonTableExpressionMixin,PivotClauseMixin,UnpivotClauseMixin,ExplainMixinBuilder for SELECT queries.
Provides a fluent interface for constructing SQL SELECT statements with parameter binding and validation.
Example
>>> class User(BaseModel): ... id: int ... name: str >>> builder = Select("id", "name").from_("users") >>> result = driver.execute(builder)
- __init__(*columns, **kwargs)[source]¶
Initialize SELECT with optional columns.
- Parameters:
Examples
Select(“id”, “name”) # Shorthand for Select().select(“id”, “name”) Select() # Same as Select() - start empty
- with_hint(hint, *, location='statement', table=None, dialect=None)[source]¶
Attach an optimizer or dialect-specific hint to the query.
- Parameters:
- Return type:
Self- Returns:
The current builder instance for method chaining.
- for_update(*, skip_locked=False, nowait=False, of=None)[source]¶
Add FOR UPDATE clause to SELECT statement for row-level locking.
Add FOR SHARE clause for shared row-level locking.
Add FOR KEY SHARE clause (PostgreSQL-specific).
FOR KEY SHARE is like FOR SHARE, but the lock is weaker: SELECT FOR UPDATE is blocked, but not SELECT FOR NO KEY UPDATE.
- Return type:
Self- Returns:
Self for method chaining
More Examples¶
Query Builder for query modifiers and joins.
Query Builder for focused workflows.