bijection/schema.ts
Joins
.leftJoin(right, options) and .innerJoin(right, options) match each row of
the current relation with rows of right whose fields are equal:
The two kinds differ only when there’s no match:
leftJoinkeeps the left row. The right side’s fields are optional in the output type, socustomer_nameabove isstring | undefined.innerJoindrops the left row.
The right side
The right side of a join is either:- A table, written
q.table(...), optionally with.as()and.filter(). Name an index on that table withindex. The index must begin with therightfield. To join on the right table’s_id, use the built-in"by_id"index. - A grouped relation, written
q.table(...).groupBy(...).aggregate(...), optionally with.as(). Don’t passindex. See Grouping.
.select() or with another join isn’t supported.
Give both sides an alias with .as(). A join combines the fields of both rows,
and Bijection refuses a join whose two sides have a field with the same name.
Cardinality
A view has one row per key, so joins in a view’s main chain usecardinality: "one": at most one right row may match each left
row. Bijection checks this as it reads. If a second row matches, the read fails
with a ViewCardinalityViolation error rather than returning two rows for one
key.
Make sure the data can’t have two matches:
- Join on the right table’s
_id, asorder_detailsdoes. - Join a link type on an endpoint whose maximum is
1. - Join a grouped relation on its grouping key, which has one row per group.
cardinality: "many" join in the main chain would produce several rows for
one key, so Bijection refuses that view when you deploy.
Grouping
.groupBy(fields).aggregate({ ... }) returns one row per distinct combination of
the grouping fields, with one output field per aggregate:
bijection/schema.ts
customer_id, order_count and total_cents. A grouping field given as a
path, such as "order.customer_id", is named after its last segment.
q.sum requires the field to be declared as v.int64() or v.number(), and
every row in the group to have a finite value for it. Counts are exact integers,
and so are sums of v.int64() fields.
Joining a grouped relation
A grouped relation doesn’t have a key of its own, so it can’t be a view by itself. Join it to the view’s key table on its complete grouping key, in the order you grouped by:bijection/schema.ts
orders has by_customer on ["customer_id"], which Bijection uses to read
one customer’s orders without scanning the table.
A group with no rows has no grouped row. A customer without orders therefore
has no match in
orderTotals, and with leftJoin its order_count is
undefined, not 0n.Missing and null values
A missing field and a field set tonull are different values for grouping and
joins. Rows where region is missing form one group, rows where region is
null form another, and each only matches rows with the same kind of value.
Compound join keys
left and right can each be an array of one to three distinct fields, with
the same length on both sides. A row matches when every pair of fields is
equal:
bijection/schema.ts
right fields in order. A grouped right side must be joined on its
complete grouping key, in the order of groupBy; a prefix of the key isn’t
enough to guarantee one match. Here sales needs an index beginning with
["region", "product_code"].
Exact decimal and distribution aggregates
These aggregates add exact decimal arithmetic and distribution statistics toaggregate:
bijection/schema.ts
- Decimals.
decimalSumanddecimalPercentileread a field declared asv.string()holding decimal text such as"1250.40". Values are never converted to floating point.scaleis an integer from 0 to 38. A value that would need rounding to fitscale, a value that isn’t a decimal, and a result with more than 38 digits fail the read with anInvalidDecimalorDecimalOverflowerror. - Percentiles.
basisPointsis an integer from 0 to 10,000, where 5,000 is the median. The result is the first observed value at which the cumulative share of rows reaches the requested fraction, and 0 selects the minimum. It never interpolates between values or approximates.percentilerequires av.int64()orv.number()field. - Distinct counts. A missing value and
nullcount as two different values.
countDistinct and the percentiles keep the group’s values while they’re
computed, and those bytes count against the view’s
max_bytes limit.