Scene 01
Virtual columns
A derived column is declared in the model and computed by each query. It never becomes a column of the database. Migration ignores it. A query reads it with a dollar sign and its name, like a physical column. asqueel has two declarations for this lesson. aliasColumn reads a column through a relation path. formulaColumn computes an SQL expression, a scalar sub-select, or an EXISTS test.
Example & screen notes
Slide title "Columns computed by the query". Table:
| Declaration | Computes |
|---|---|
aliasColumn(name, relation_path=...) |
a column of a related table |
formulaColumn(name, sql_formula=...) |
an SQL expression |
formulaColumn(name, select=dict(...)) |
a scalar sub-select |
formulaColumn(name, exists=dict(...)) |
an EXISTS test |
Bottom line: "No column in the database. Read with $name."
Scene 02
aliasColumn
An alias column gives a name to a relation path. state_name reads the name of the state of the customer. region_name crosses two relations. In the SQL, the alias becomes the same LEFT JOIN as the path. The result column carries the alias name, not the path name. The alias takes the dtype and the attributes of its target column. state_name has dtype A, like the name column of the state. When the target carries a relation, the alias inherits it, as lesson 05 showed.
Example & screen notes
Slide title "aliasColumn". Code from the course model:
tbl.aliasColumn('state_name', relation_path='@state.name')
tbl.aliasColumn('region_name', relation_path='@state.@region_code.name')
db.table('invc.customer').query(columns='$account_name, $state_name, $region_name')
SELECT "t0"."account_name" AS "account_name",
"t1"."name" AS "state_name",
"t2"."name" AS "region_name"
FROM "invc"."invc_customer" AS "t0"
LEFT JOIN "invc"."invc_state" AS "t1" ON (("t0"."state")="t1"."code")
LEFT JOIN "invc"."invc_region" AS "t2" ON (("t1"."region_code")="t2"."code")
Scene 03
formulaColumn with sql_formula
sql_formula is an SQL expression. Inside it, a dollar name is a column of the same table. An at path reaches a related table and adds its join. The compiler replaces the references and puts the expression between brackets. full_address joins two text columns. value_category is a CASE expression. The formula has no type that asqueel can infer. Declare it with dtype. Without dtype, a formula column has dtype A.
Example & screen notes
Slide title "sql_formula". Code from the course model:
tbl.formulaColumn('full_address', sql_formula="$street_address || ', ' || $suburb", dtype='T')
tbl.formulaColumn('value_category', sql_formula=(
"CASE WHEN $total > 1000 THEN 'High' WHEN $total > 100 THEN 'Medium' ELSE 'Low' END"), dtype='A')
( "t0"."street_address" || ', ' || "t0"."suburb" ) AS "full_address"
Bottom line: "No dtype: A."
Scene 04
select: a correlated sub-select
select describes a sub-select as a dictionary. table is the table of the sub-select. columns is what it returns. where is its filter. Inside the dictionary, a dollar name belongs to the sub-select table. hash THIS dot id belongs to the row that owns the formula, here the customer. That is how the sub-select is correlated with the outer row. The sub-select must return exactly one column. asqueel adds no LIMIT. If it returns more than one row, PostgreSQL raises an error. The dictionary also accepts order_by and limit. The latest invoice in lesson 05 uses them.
Example & screen notes
Slide title "select=dict(...) and #THIS". Code:
tbl.formulaColumn('n_invoices', select=dict(
table='invc.invoice', columns='COUNT(*)', where='$customer_id=#THIS.id'), dtype='L')
tbl.formulaColumn('invoiced_total', select=dict(
table='invc.invoice', columns='SUM($total)', where='$customer_id=#THIS.id'), dtype='N')
( ( SELECT COUNT(*) AS "COUNT___"
FROM "invc"."invc_invoice" AS "t0_t0"
WHERE ("t0_t0"."customer_id"="t0"."id") ) ) AS "n_invoices",
Scene 05
exists
exists has the same dictionary, without columns. It becomes an EXISTS test. The result is true when the sub-select finds at least one row. Declare dtype B. asqueel does not infer a boolean from the SQL. A formula column takes exactly one of sql_formula, select or exists. Two of them together raise a ValueError.
Example & screen notes
Slide title "exists=dict(...)". Code:
tbl.formulaColumn('has_invoices', exists=dict(
table='invc.invoice', where='$customer_id=#THIS.id'), dtype='B')
( EXISTS ( SELECT 1 AS "_1"
FROM "invc"."invc_invoice" AS "t0_t0"
WHERE ("t0_t0"."customer_id"="t0"."id") ) ) AS "has_invoices",
Scene 06
Named sub-selects inside a formula
To use a sub-select inside a larger expression, give it a name. An argument select underscore name holds the dictionary. The formula refers to it as hash and the name. Here hash top_note is the text of the note with the lowest priority. COALESCE returns No notes when the invoice has none. exists underscore name works the same way and becomes an EXISTS test. Every named sub-select must appear in the formula. An unused one raises a ValueError.
Example & screen notes
Slide title "select_examples/test_invoice_pg/invc.py:
invoice.formulaColumn('priority_note', sql_formula="COALESCE(#top_note, 'No notes')",
select_top_note=dict(table='invc.invoice_note', columns='$note_text',
where='$invoice_id=#THIS.id', order_by='$priority', limit=1),
dtype='T')
Rows:
| inv_number | priority_note |
|---|---|
| A-001 | Fragile |
| A-002 | No notes |
Scene 07
var_*: values bound to the formula
A formula can have its own parameters. Each argument var underscore name gives a value. The formula refers to it as colon and the name, without var. The value is a bound parameter, not SQL text. The compiler gives it a name that belongs to this formula, here f2 underscore high_label. So two formulas can use the same parameter name without a conflict. In a formula column, var values are Python values. In a virtual relation, lesson 07, they are SQL expressions of the source row.
Example & screen notes
Slide title "var_*". Code, an addition copied from examples/test_invoice_pg/invc.py:
invoice.formulaColumn('status_label', sql_formula=(
'CASE WHEN $total > 1000 THEN :high_label '
'WHEN $total > 100 THEN :mid_label ELSE :low_label END'),
var_high_label='Premium', var_mid_label='Standard', var_low_label='Basic', dtype='T')
( CASE WHEN "t0"."total" > 1000 THEN %(f2_high_label)s
WHEN "t0"."total" > 100 THEN %(f2_mid_label)s ELSE %(f2_low_label)s END ) AS "status_label"
Scene 08
A formula that uses other formulas
A formula can refer to other formulas and aliases of the same table. The compiler expands each reference into its own expression. line_gross uses line_total and line_vat, and line_vat uses line_total. The final SQL contains only physical columns. avg_invoice_value divides two sub-select formulas. Each sub-select appears again in the expression. A formula that refers back to itself, directly or through others, raises a ValueError.
Example & screen notes
Slide title "Formulas compose". Code:
customer.formulaColumn('avg_invoice_value',
sql_formula='$invoiced_total / NULLIF($n_invoices, 0)', dtype='N')
row.formulaColumn('line_vat', sql_formula='$line_total * $vat_rate / 100', dtype='N')
row.formulaColumn('line_gross', sql_formula='$line_total + $line_vat', dtype='N')
( ( "t0"."quantity" * "t0"."unit_price" ) + ( ( "t0"."quantity" * "t0"."unit_price" )
* "t0"."vat_rate" / 100 ) ) AS "line_gross"
Scene 09
static=True
A query with columns star returns the physical columns of the table. It also returns the virtual columns declared with static True. Other virtual columns are left out of star. Here the alias invoice_date and the formula product_code are static. They follow the physical columns in the result. The formula product_code also shows an at path inside sql_formula. Lesson 10 shows which physical columns star leaves out.
Example & screen notes
Slide title "What * returns". Code:
row.aliasColumn('invoice_date', relation_path='@invoice_id.date', static=True)
row.formulaColumn('product_code', sql_formula='@product_id.code', dtype='T', static=True)
db.table('invc.invoice_row').query(columns='*')
Last lines of the SELECT list:
"t0"."vat_rate" AS "vat_rate",
"t1"."date" AS "invoice_date",
( "t2"."code" ) AS "product_code"
Scene 10
Recap
An alias names a path, and a formula is an expression, a sub-select or an EXISTS test. hash THIS correlates sub-selects, named sub-selects compose formulas, and var arguments are bound values. Declare dtype on every formula, and mark with static the virtual columns that star must return. The next lesson covers formulas supplied by table methods.
Example & screen notes
Three bullets:
aliasColumnnames a relation path;formulaColumntakes one ofsql_formula,select,exists#THIScorrelates a sub-select;select_<name>is used as#<name>;var_*are bound values- Without
dtypea formula isA;static=Trueadds a virtual column to*