Factory¶
The SQLFactory class is the main entry point for the query builder system.
It provides methods to create SELECT, INSERT, UPDATE, DELETE, MERGE, and DDL
builders, plus COPY statement generation.
SQLFactory¶
- 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:
- 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:
- 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_table_as_select(dialect=None)[source]¶
Create a CREATE TABLE AS SELECT builder.
- 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:
- 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(column='*', distinct=False)[source]¶
Create a COUNT expression.
- Parameters:
- Return type:
- Returns:
COUNT expression.
- count_distinct(column)[source]¶
Create a COUNT(DISTINCT column) expression.
- Parameters:
column¶ (
Union[str,Expression,ExpressionWrapper,Case]) – Column to count distinct values.- Return type:
- Returns:
COUNT DISTINCT expression.
- 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:
- 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:
- 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:
- 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:
- 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:
- Returns:
MIN() OVER() window function expression.
- static sum(column, distinct=False)[source]¶
Create a SUM expression.
- Parameters:
- Return type:
- Returns:
SUM expression.
- static avg(column)[source]¶
Create an AVG expression.
- Parameters:
column¶ (
Union[str,Expression,ExpressionWrapper,Case]) – Column to average.- Return type:
- Returns:
AVG expression.
- static max(column)[source]¶
Create a MAX expression.
- Parameters:
column¶ (
Union[str,Expression,ExpressionWrapper,Case]) – Column to find maximum.- Return type:
- Returns:
MAX expression.
- static min(column)[source]¶
Create a MIN expression.
- Parameters:
column¶ (
Union[str,Expression,ExpressionWrapper,Case]) – Column to find minimum.- Return type:
- Returns:
MIN expression.
- static rollup(*columns)[source]¶
Create a ROLLUP expression for GROUP BY clauses.
- static cube(*columns)[source]¶
Create a CUBE expression for GROUP BY clauses.
- static any(values)[source]¶
Create an ANY expression for use with comparison operators.
- static not_any_(values)[source]¶
Create a NOT ANY expression for use with comparison operators.
- static concat(*expressions)[source]¶
Create a CONCAT expression.
- static upper(column)[source]¶
Create an UPPER expression.
- static lower(column)[source]¶
Create a LOWER expression.
- static length(column)[source]¶
Create a LENGTH expression.
- Parameters:
- Return type:
- Returns:
LENGTH expression.
- static round(column, decimals=0)[source]¶
Create a ROUND expression.
- Parameters:
- Return type:
- Returns:
ROUND 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:
- Returns:
CASE expression equivalent to DECODE.
- static cast(column, data_type)[source]¶
Create a CAST expression for type conversion.
- Parameters:
- Return type:
- Returns:
CAST expression.
- static coalesce(*expressions)[source]¶
Create a COALESCE expression.
- Parameters:
- Return type:
- Returns:
COALESCE expression.
- 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:
- 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:
- Returns:
INSERT expression with placeholders for bulk operations
- static case()[source]¶
Create a CASE expression builder.
- Return type:
- 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:
- 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:
- Returns:
LEAD window function expression.
Example
>>> sql.lead("salary", offset=1, order_by="hire_date") >>> sql.lead("price", partition_by="category", order_by="date")
Convenience Instance¶
- sqlspec.builder.sql: SQLFactory¶
Pre-configured
SQLFactoryinstance for convenient query building.Callable as
sql(statement, dialect=None)to create a builder from a SQL string.
COPY Statement Helpers¶
- sqlspec.builder.build_copy_statement(*, direction, table, location, columns=None, options=None, dialect=None)[source]¶
- Return type:
Temporal Table Support¶
- sqlspec.builder.create_temporal_table(table, as_of, kind=None)[source]¶
Create a table expression with temporal (time-travel) version clause.
- Parameters:
- Return type:
Table- Returns:
Table expression with version clause that generates dialect-specific SQL.
Notes
Inputs are normalized before building the
exp.Versionclause so both string table names and literal timestamps work consistently.Example
>>> from sqlspec.builder import create_temporal_table >>> from sqlglot import exp >>> t = create_temporal_table( ... "orders", exp.Literal.string("2024-01-01") ... ) >>> t.sql(dialect="oracle") "orders AS OF TIMESTAMP '2024-01-01'" >>> t.sql(dialect="bigquery") "orders FOR SYSTEM_TIME AS OF '2024-01-01'"
- sqlspec.builder.register_version_generators()[source]¶
Register dialect-specific version_sql generators.
This function is idempotent - calling it multiple times has no effect after the first call. It is called automatically when the builder module is imported.
Registers custom SQL generators for temporal (time-travel) queries: - Default (no dialect): AS OF SYSTEM TIME (CockroachDB style) - Oracle: AS OF TIMESTAMP / AS OF SCN - Snowflake: AT (TIMESTAMP => …) / BEFORE (…) - DuckDB: AT (TIMESTAMP => …) - Postgres/CockroachDB: AS OF SYSTEM TIME - BigQuery: Uses built-in FOR SYSTEM_TIME AS OF
- Return type: