Conversation
# Conflicts: # datajunction-server/datajunction_server/models/deployment.py
robinld
force-pushed
the
robind/fixed-grain
branch
from
September 9, 2026 21:52
7b33889 to
97ee562
Compare
robinld
marked this pull request as ready for review
September 9, 2026 22:09
robinld
force-pushed
the
robind/fixed-grain
branch
from
September 10, 2026 00:51
97ee562 to
c27a21a
Compare
robinld
force-pushed
the
robind/fixed-grain
branch
from
September 10, 2026 06:35
c27a21a to
9d1b371
Compare
shangyian
reviewed
Sep 11, 2026
An experimentation metric is a ratio of two unit-grain metrics: the activity each allocated unit contributed, over the allocated population. Writing that shape down here records where this branch stops. Two refusals land on the shape rather than on an edge case. A population written as COUNT(DISTINCT unit) at unit grain is refused, though at unit grain the count is one per unit and the merge is a plain sum. And the ratio always spans two facts, since the population comes from the allocation table, so the cross-fact refusal applies to every one of these metrics. The global grain the refusal suggests does build, and answers a different question: OVER () broadcasts the total across every group, so the result divides an all-time total by one month's population. The last test is xfail rather than an assertion on the generated SQL. A fixed-grain dimension outside the query grain never reaches the measures CTE, so the window partitions by a column that does not exist. Pinning that SQL would lock in something no engine accepts, so it asserts only the invariant: a column read from a CTE has to be projected by it.
betodealmeida
force-pushed
the
semi-additive-metrics
branch
from
September 22, 2026 18:10
76a7dc0 to
ed0937a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the
fixed_grainhalf of #2245. A base metric can declare the grain at which its aggregate is computed, and DJ broadcasts that value when the query requests a finer grain.This PR is stacked on #2502, which implements the complementary
reaggregatedirection. It can be retargeted tomainafter #2502 lands.fixed_grainhas three forms:[]: compute at the global grain[dimension, ...]: compute at the declared partition grainFor example, querying
total_revenueat a finer grain generates the equivalent of:The inner aggregate computes the query-group contribution. The outer window re-merges those contributions within the declared fixed grain. A non-empty grain produces
OVER (PARTITION BY ...)instead.Implementation
sql/decompose.pyvalidates that the declaration belongs to one freely aggregatable base component, propagates it through decomposition, and rejects a later window over a broadcast value.construction/build_v3/cube_matcher.pyskips a cube that does not materialize a metric's partition dimension, mirroringvalidate_cube_covers_reaggregate_dimensions. A cube that does carry it is used normally.Current constraints
The initial implementation accepts only cases where broadcasting can be expressed without changing the metric's meaning.
COUNT(DISTINCT ...), a multi-component metric (AVG, variance), or a non-decomposable aggregatereaggregatedimension gets. The result stays the shape that was requestedCOALESCE, so a partition naming one group's column is ungroupedMAX/AVGover it are allowed, as is referencing it beside a window — e.g. a trailing sum divided by a global totalRelated fixes
reaggregateandfixed_graindeclarations do not become part of shared measure state or identity.extractedMeasuresloader'sload_only. Reading a field that loader omits raisesMissingGreenleton a cold session, which the batch handler turns into a silent null rather than an error.Out of scope
{hierarchy, drill_up}frameprimitivereaggregategained create/edit fields and node-page display in feat: semi-additive measures #2502; the equivalent forfixed_grainis deferred to a follow-up, so the declaration is currently settable only through deployment specs and the API.Known gaps
POST /nodes/metricvalidation does not yet reject an unresolvable fixed-grain column, although deployment validation does. The invalid declaration is rejected later when a query attempts to use it.Testing
Coverage includes global and partitioned broadcasts, derived ratios, declaration round trips, namespace copying, semantic identity, frozen-measure reuse, GraphQL decomposition loading, window refusal, and cube matching and materialization carrying the broadcast.