In OData, when would a client use $apply with groupby and aggregate instead of 4.01's $compute, and how does each change the response?
answer
- change the set or the row
- transformations chained by slashes
- aliases become dynamic properties
- $apply is evaluated first
basics
~20 s$apply transforms the collection itself: groupby and aggregate return one row per group with aliased totals. $compute, new in OData 4.01, adds a calculated property to each existing row without changing how many rows come back.
solid answer
~40 s`$apply` comes from the OData **Data Aggregation** extension, a separate OASIS Committee Specification rather than part of the core 4.01 standard. Its value is a sequence of **transformations** separated by `/`, such as `filter(Status eq 'Shipped')/groupby((Customer/City),aggregate(Amount with sum as Total))`; it returns one row per city, the grouped rows are not addressable entities, and the alias `Total` becomes a dynamic property. `$apply` is evaluated first, so a later `$filter` or `$orderby` can use `Total`. `$compute`, a core 4.01 option, takes `expression as Name` items, such as `Price mul Quantity as LineTotal`, and adds that dynamic property to every row, usable in `$select`, `$filter` and `$orderby`. Use `$apply` when the client needs totals per group; use `$compute` when it needs a derived value on each row.
go deeper
Recall that $apply can group and total rows on the service, while $compute adds a calculated property to each row.
Explain transformation sequences, the with and as keywords, and why grouped rows carry dynamic properties but no entity-id.
Show how $apply's evaluation-first rule lets later options use aggregate aliases, and how a service advertises which transformations it supports.
Weigh exposing server-side aggregation to clients against shipping fixed reporting endpoints, given the scan cost an arbitrary groupby can create.
## Two tools for two shapes Both options make the service calculate something the model does not store, but they change the response in different ways: | | `$apply` (Data Aggregation extension) | `$compute` (core OData 4.01) | |---|---|---| | Changes | the **collection**: rows can be grouped, filtered, aggregated | each **row**: a new property is added | | Rows returned | typically fewer — one per group, or one in total | the same rows as without it | | Syntax | transformations chained with `/` | comma-separated `expression as Name` items | | Typical question | "total sales per city" | "line total for each item" | | Where defined | OData Extension for Data Aggregation Version 4.0, a Committee Specification | OData 4.01 Protocol and URL Conventions | ## How $apply works `$apply` takes a **transformation sequence**: each transformation consumes an input set and produces an output set, and the output of the last one is the result. The first input set is the collection the resource path addresses. `$apply` MUST NOT be used on a single instance. - `aggregate(Amount with sum as Total)` returns a single instance holding the aggregated value. Standard aggregation methods are `sum`, `min`, `max`, `average` and `countdistinct`. - `sum` applies to numeric values and returns null if there are no values to aggregate; null values are removed before any standard method runs, and `countdistinct` counts the distinct values. - The keyword **`as`** names the result; the alias becomes a **dynamic property** in the response. - `aggregate($count as OrderCount)` counts the input set; the `$count` aggregate expression MUST specify an alias and MUST NOT specify an aggregation method. - `groupby((Customer/City),aggregate(Amount with sum as Total))` returns one instance per distinct city, each holding the grouping property and the total. The grouped instances are projections **without an entity-id** — rows of a report, not addressable entities. - Other transformations include `filter`, `orderby`, `search`, `skip`, `top`, `topcount`, `compute`, `concat`, `identity` and `join`. ```http GET /odata/Orders?$apply=filter(Status eq 'Shipped') /groupby((Customer/City),aggregate(Amount with sum as Total)) &$filter=Total gt 10000&$orderby=Total desc ``` ## How $apply fits the evaluation order The extension says `$apply` is **evaluated first**, and the other system query options are then evaluated on its result — consistent with the 4.01 Protocol's order, which puts `$apply` before `$compute`, `$search`, `$filter`, `$count`, `$orderby`, `$skip` and `$top`. That is why the request above can filter and sort on `Total`: by then `Total` is a property of each grouped row, much like a `HAVING` clause in SQL. Because grouped output has no inherent order between groups, the extension requires a stable total order before `$skip` and `$top` page through it. ## How $compute works `$compute` is new in OData 4.01 (absent from 4.0): 1. Its value is a comma-separated list of **compute instructions**, each a common expression, the keyword `as`, and a name: `$compute=Price mul Quantity as LineTotal`. 2. The name MUST differ from the names of declared or dynamic properties of the resources — a computed property cannot override a stored one. 3. The computed property can be used in `$select`, `$filter` and `$orderby`: `Items?$compute=Price mul Quantity as LineTotal&$filter=LineTotal gt 100&$orderby=LineTotal desc`. 4. Computed properties SHOULD be included in the result as dynamic properties, and MUST be when `$select` names them or uses `*`. 5. `$compute` is also allowed as an expand option, so each customer's expanded orders can carry a computed value. ## Choosing between them - If the client is about to download every row only to add numbers up, `$apply` moves the summation to the service and returns a handful of rows. - If the client needs the rows themselves, with a derived value to display, filter or sort by, `$compute` keeps the rows and adds the value. - The two combine: `$apply` itself defines a `compute` transformation for use inside a sequence. ## What it costs the service Aggregation pushes real work to the server: a `groupby` over millions of rows is a full scan unless the storage is shaped for it. A service says what it supports: `ComputeSupported` in the Capabilities vocabulary tags whether `$compute` works on a collection, and the aggregation vocabulary's `ApplySupported` term can list the supported `Transformations`. A service that does not support `$apply` at all must fail a request carrying it, as with any unsupported system query option.
- In `Sales?$apply=groupby((Customer),aggregate(Amount with sum as Total))&$filter=Total gt 1000`, what does the OData $filter operate on?On the grouped output of `$apply`. The Data Aggregation extension says `$apply` is evaluated first and the other system query options are then evaluated on its result, so `$filter` sees one row per customer with the dynamic property `Total` and keeps customers whose total exceeds 1000 — the effect of a SQL `HAVING` clause.
- Can an OData 4.01 client name a computed property `Price` when the entity already has a declared `Price`?No. The name given after `as` in a `$compute` instruction MUST differ from the names of declared or dynamic properties of the identified resources. `$compute` only adds new dynamic properties; it cannot replace or override a stored value, so a client picks a fresh name such as `DiscountedPrice`.
saying these in an interview costs you the question
- $compute groups rows, like a SQL GROUP BY
- $apply is part of the core OData 4.01 standard every service supports
- $filter runs before $apply, so it cannot use an aggregate alias
- A computed property may reuse a declared property's name to override it
- The $count aggregate expression takes an aggregation method such as sum