Skip to content

v1.0.0: Consistent, fully composable query expressions

Latest

Choose a tag to compare

@ZmeiGorynych ZmeiGorynych released this 24 Sep 09:59
· 112 commits to main since this release
d085f63

SLayer 1.0.0

SLayer 1.0 is the release where the query language becomes consistent: if a query makes sense, it should run, and if it's impossible to resolve it to correct SQL, it should fail with an error that tells you why. Most of the work since 0.10.2 removes "not supported" refusals for queries that were well-defined, and closes the remaining cases where SLayer quietly returned plausible but wrong numbers.

More queries just work

  • window= now works on every aggregation, not only sum and avg: rolling count, min, max, count_distinct, statistics, your custom aggregations, and first/last (the earliest or latest value within the window). Parameters such as weight= are read per row within the window.
  • Downstream stages of a multi-stage query can now use time dimensions on their upstream's date columns, so you can, for example, bucket each customer's last order date by month in a second stage. time_shift, change, cumsum, windows and date_range all work there too.
  • Aggregating an expression that mixes columns from joined models, such as sum(amount - customers.discount), now works, as does nesting a transform inside an aggregation (sum(cumsum(x) - 1)) or passing one as a parameter (weighted_avg(price, weight=rank(...))).
  • Filters that mix columns from several joined models under OR or NOT, such as tier = 'bronze' or orders.status = 'ok', are now applied correctly instead of being dropped with a warning.
  • A transform's own partition_by= can now name a computed dimension, just like an aggregation's can.
  • Column SQL containing :word, for example a regex like (?:...), no longer fails with a "value is required for bind parameter" error.

Fixes for silently wrong numbers

  • Filtering on a joined one-to-many model (e.g. customers filtered by orders.status = 'ok') no longer double-counts customers in partitioned, windowed, first/last or raw-row queries. SLayer now keeps each customer that has at least one matching order, counted once.
  • time_shift, change and change_pct over an expression that contains a partition_by= aggregation, a first/last, or a windowed aggregation now keep each part's own grouping instead of recomputing it wrongly.
  • A time dimension finer than a column's existing bucket (a day dimension over a column already truncated to month) is now an error instead of month rows labelled as days. You can declare this on your own columns with the new Column.granularity field; SLayer sets it automatically on query-backed models.

Breaking changes

  • Saving a model now rejects a derived column or column filter that reaches across a one-to-many join (it is a set of values per row, not a single value; use an aggregation instead) or that loops back to a model already on its path. If SLayer cannot prove whether a join fans out, you get a warning instead.
  • join_pairs in a model's joins must name declared columns by their name, not by expression columns or raw physical names. Stored models that use the physical name of a renamed column are migrated automatically when loaded; models that join on undeclared or expression columns need fixing. The Cube, dbt and OSI importers now produce compliant joins.
  • A column with a filter now behaves exactly like CASE WHEN filter THEN value END everywhere, including as a dimension, in filters, in partition_by= and as an aggregation parameter.
  • time_shift, change and change_pct on a plain row column that is not one of the query's dimensions are now rejected (they used to multiply result rows).
  • A rank-family transform's own partition_by= must be part of the grouping of the value it ranks, and repeating a keyword argument in any call is now an error.
  • Queries where partition_by= or a first/last ordering column would double-count across a one-to-many join now fail with an error naming the join, in every to_many_handling mode.

The docs and examples now use the functional aggregation syntax (sum(amount)) throughout; the colon form (amount:sum) still works but is deprecated.