SELECT Queries

Builds SELECT queries using SqlSelect.

All SQL output below shows the result of get_sql(). Where queries have parameters, get_args() is shown alongside.

Basic Select

qb = (SQL.select()
      .from_table('films')
      .column('title')
      .column('director')
      .where('year', '>', 2000))
SELECT title, director
FROM films
WHERE year > %s

-- get_args(): [2000]

Multiple Columns

Add columns individually or in bulk:

# Individual
qb.column('name', 'alias')

# Bulk
qb.columns('col1', 'col2', 'col3')

# With aliases (tuple form)
qb.columns(('count(*)', 'total'), 'name')

The columns() method processes each item through SqlColumn.from_list(): strings become SqlColumn(name), and tuples are unpacked as positional arguments to SqlColumn(*item), so ('count(*)', 'total') becomes SqlColumn('count(*)', 'total'). For clarity, prefer passing explicit SqlColumn instances:

qb.columns(SQL.column('count(*)', 'total'), 'name')

DISTINCT

PostgreSQL only: DISTINCT ON

qb = (SQL.select()
      .from_table('weather_reports')
      .distinct('location')
      .columns('location', 'time', 'report'))
SELECT
    DISTINCT ON (location)
    location, time, report
FROM weather_reports

Tables and Joins

Multiple tables:

qb = (SQL.select()
      .from_table('employees', 'e')
      .from_table('departments', 'd')
      .column('e.name')
      .where('e.dept_id = d.id'))
SELECT e.name
FROM
    employees e,
    departments d
WHERE e.dept_id = d.id

Joins:

qb = (SQL.select()
      .from_table('employees', 'e')
      .join(SQL.join('JOIN', 'departments', 'd', 'd.id = e.dept_id'))
      .column('e.name')
      .column('d.name', 'dept_name'))
SELECT e.name, d.name AS dept_name
FROM
    employees e
    JOIN departments d ON d.id = e.dept_id

Convenience methods for common join types:

qb.join_table('departments', 'd', 'd.id = e.dept_id')
qb.left_join_table('managers', 'm', 'm.id = e.manager_id')

Subqueries as tables:

sub = (SQL.select()
       .from_table('orders')
       .column('customer_id')
       .column('sum(amount)', 'total')
       .group_by('customer_id'))

qb = (SQL.select()
      .from_table(sub, 'totals')
      .column('*'))
SELECT *
FROM
    (
        SELECT customer_id, sum(amount) AS total
        FROM orders
        GROUP BY customer_id
    ) totals

WHERE Clause

The .where() method accepts the same parameter variants as SqlTerm.

Simple conditions:

qb.where('status', '=', 'active')

Complex conditions using boolean expressions (see SqlAnd):

qb.where(SQL.and_expr()
         .add('status', '=', 'active')
         .add('age', '>', 18))
WHERE
    status = %s
    AND age > %s

-- get_args(): ['active', 18]

NULL handling (automatic):

# value=None with '=' becomes IS NULL
SQL.term('deleted_at', '=', None)

Subquery in WHERE:

qb.where('id', 'IN',
         SQL.select()
         .from_table('active_users')
         .column('id'))
WHERE
    id IN (
        SELECT id
        FROM active_users
    )

GROUP BY and HAVING

qb = (SQL.select()
      .from_table('films')
      .column('kind')
      .column('sum(len)', 'total')
      .group_by('kind')
      .having(SQL.cat('sum(len) < interval ', SQL.value('5 hours'))))
SELECT kind, sum(len) AS total
FROM films
GROUP BY kind
HAVING sum(len) < interval %s

-- get_args(): ['5 hours']

ORDER BY, LIMIT, OFFSET

qb = (SQL.select()
      .from_table('products')
      .column('name')
      .order_by('price DESC', 'name')
      .limit(10)
      .offset(20))
SELECT name
FROM products
ORDER BY price DESC, name
LIMIT 10
OFFSET 20

WINDOW Clauses

PostgreSQL only.

See PgWindow for the full window API.

qb = (SQL.select()
      .from_table('employees')
      .column('name')
      .column('salary')
      .column('avg(salary) OVER w', 'dept_avg')
      .window(SQL.window('w')
              .partition_by('department')
              .order_by('salary DESC')))
SELECT name, salary, avg(salary) OVER w AS dept_avg
FROM employees
WINDOW
    w AS (
        PARTITION BY department
        ORDER BY salary DESC
    )

Common Table Expressions (WITH)

See SqlCte for the CTE API.

qb = (SQL.select()
      .with_cte_query('active',
                      SQL.select()
                      .from_table('users')
                      .column('*')
                      .where('active', '=', True))
      .from_table('active')
      .column('name'))
WITH active AS (
    SELECT *
    FROM users
    WHERE active = %s
)
SELECT name
FROM active

-- get_args(): [True]

Materialized CTEs (PostgreSQL only):

qb.with_cte(SQL.cte('expensive_query', some_select).materialized())

Recursive CTEs:

qb = (SQL.select()
      .recursive()
      .with_cte_query('tree', recursive_query)
      .from_table('tree')
      .column('*'))

UNION and Set Operations

See SqlUnion for the union API.

qb = (SQL.select()
      .column('name')
      .from_table('employees')
      .union_query(SQL.select()
                   .column('name')
                   .from_table('contractors')))
SELECT name
FROM employees
UNION
SELECT name
FROM contractors

For UNION ALL:

qb.union(SQL.union('UNION ALL', other_select))

SELECT INTO

PostgreSQL only.

qb = (SQL.select()
      .column('*')
      .from_table('films')
      .into('films_recent')
      .where('date_prod', '>=', '2002-01-01'))
SELECT *
INTO films_recent
FROM films
WHERE date_prod >= %s

-- get_args(): ['2002-01-01']

FETCH

PostgreSQL only.

qb = (SQL.select()
      .columns('id', 'name')
      .from_table('employees')
      .order_by('salary DESC')
      .fetch(SQL.cat('FIRST ', 5, ' ROWS ONLY')))
SELECT id, name
FROM employees
ORDER BY salary DESC
FETCH FIRST 5 ROWS ONLY

FOR UPDATE / Locking

PostgreSQL only.

See PgForLocking for the full locking API.

Simple locking:

qb.for_locking('UPDATE')

Advanced locking:

qb.for_locking(SQL.for_locking('NO KEY UPDATE')
               .of('employees', 'tasks')
               .locking('SKIP LOCKED'))
FOR NO KEY UPDATE OF employees, tasks SKIP LOCKED

Subqueries

Pass a SqlSelect as the table argument to from_table() to use it as a derived table. The formatter automatically wraps it in parentheses:

sub = (SQL.select()
       .from_table('orders')
       .column('max(amount)')
       .where('customer_id = c.id'))

qb = SQL.select().from_table(sub, 'o').column('*')
SELECT *
FROM
    (
        SELECT max(amount)
        FROM orders
        WHERE customer_id = c.id
    ) o