Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Named queries and def

Named queries

Brackets make a query a value, as they make any long Python expression span lines. A named query is read with from, like a table:

open_cases = (
    from cases
    where status == "open"
)

from open_cases
group by band:
    n = count()
SELECT band, count(*) AS n
FROM cases
WHERE status = $1
GROUP BY band
-- parameters: ["open"]

A named query used once is written in place, where the database can optimise across it, rather than as a separate step it must work out first.

Values

A name given a value before the query can be used in it. The name means the value even where a table has a column of the same name, so a new column can never change what a query does:

threshold = 1_000_000

from transactions
where amount > threshold
SELECT *
FROM transactions
WHERE amount > 1000000

def: steps written once

A def holds steps. Calling it on a line of its own puts them in place:

def recent(days_back=7):
    """The rows raised in the last `days_back` days."""
    where raised > now() - days(days_back)

from cases
recent(30)
where assignee is None
take 10
SELECT *
FROM cases
WHERE raised > now() - INTERVAL '30 days' AND assignee IS NULL
LIMIT 10

Its parameters take defaults, and arguments by name, as in Python: recent(), recent(30) and recent(days_back=30).

Queries with arguments

A def whose body starts with from is a query. Called in a from, it is read in place:

def cases_for(account: str, days_back: int = 30):
    from cases
    where account_id == account
    where raised > now() - days(days_back)

from cases_for("A1", 7)
sort -risk
SELECT *
FROM cases
WHERE account_id = $1 AND raised > now() - INTERVAL '7 days'
ORDER BY risk DESC NULLS LAST
-- parameters: ["A1"]

Compiled on its own, its parameters become the SQL’s placeholders, given values when the query runs. With bcql, that is --query cases_for:

def cases_for(account: str, days_back: int = 30):
    from cases
    where account_id == account
    where raised > now() - days(days_back)
SELECT *
FROM cases
WHERE account_id = $1 AND raised > now() - $2 * INTERVAL '1 day'
-- parameters: [{"arg": "account"}, {"arg": "days_back"}]
-- arguments: account, days_back = 30

From Python, compile(text, query="cases_for") gives the query, and bind(account="A1") the values for its placeholders, with days_back’s default: see Python. On ClickHouse, the placeholders are named and typed:

def cases_for(account: str, days_back: int = 30):
    from cases
    where account_id == account
    where raised > now() - days(days_back)
SELECT *
FROM cases
WHERE account_id = {account:String} AND raised > now() - toIntervalDay({days_back:Int64})
-- parameters: [{"arg": "account"}, {"arg": "days_back"}]
-- arguments: account, days_back = 30