From 4dac882e58ddd347045a52a6840bdb4bba2472f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Wed, 30 Sep 2026 09:31:48 +0200 Subject: [PATCH] docs(sql): a COUNT(*) is an aggregate a view stores A COUNT(*) GROUP BY view now deploys on every supported major, so the column-name refusal proposes an alias for a conflicting COUNT(*) too. Co-Authored-By: Claude Opus 5.5 (1M context) --- documentation/sql/materialized_views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/sql/materialized_views.md b/documentation/sql/materialized_views.md index fe066fed..a8a8e3e7 100644 --- a/documentation/sql/materialized_views.md +++ b/documentation/sql/materialized_views.md @@ -535,7 +535,7 @@ DROP MATERIALIZED VIEW IF EXISTS orders_with_customers_mv; | **`RIGHT JOIN` / `FULL OUTER JOIN`** | Not supported (see below). Use `LEFT JOIN` with swapped table order. | | **Aggregates need a `GROUP BY`** | A view that selects an aggregate — a window function, a percentile or a standard deviation included — without a `GROUP BY`, or whose `GROUP BY` reaches none of the tables it reads (a `CROSS JOIN`ed table, or an alias of a table joined more than once that the view does not read: only one alias of such a table is read), is refused at creation: a materialized view computes aggregates per `GROUP BY` group, and without one it would store the source rows and none of the aggregates. The refusal names each aggregate the view would not store: compute it with a query instead. Known limitation: an aggregate nested inside a function or an expression, such as `ROUND(AVG(amount), 2)` or `SUM(amount) / COUNT(*)`, is not detected — the refusal does not name it, and does not refuse a view whose only aggregates are nested this way. | | **Aggregates of an expression** | An aggregate whose operand is an expression rather than a column — `MAX(DATE_TRUNC(created_at, MONTH))`, `SUM(CAST(amount AS INT))`, `COUNT(UPPER(name))` — is refused at creation: a materialized view aggregates a column as it is stored, so it would compute `MAX(created_at)`, `SUM(amount)` and `COUNT(*)` in their place. A `CAST` over a column is refused too, whatever its type. Compute it with a query instead. | -| **One column per aggregate** | Two aggregates that would produce the same column name — typically unaliased, `COUNT(x), COUNT(DISTINCT x)` or `SUM(amount), MAX(amount)` — are refused at creation; give each its own alias (`COUNT(x) AS n, COUNT(DISTINCT x) AS n_distinct`). The refusal proposes aliases only for the aggregates the view would store under them, and names the others — for example a window function, a percentile, or an aggregate of a column in a single-table view without a computed column — to be computed with a query. A `COUNT(*)` is named, but neither aliased nor sent to a query. | +| **One column per aggregate** | Two aggregates that would produce the same column name — typically unaliased, `COUNT(x), COUNT(DISTINCT x)` or `SUM(amount), MAX(amount)` — are refused at creation; give each its own alias (`COUNT(x) AS n, COUNT(DISTINCT x) AS n_distinct`). The refusal proposes aliases only for the aggregates the view would store under them, and names the others — for example a window function, a percentile, or an aggregate of a column in a single-table view without a computed column — to be computed with a query. A `COUNT(*)` is one of the aggregates a view stores, so the refusal proposes an alias for it too. | | **Quota limits** | Community: 1 view · Pro: 50 · Enterprise: unlimited | | **Watcher dependency (ES license)** | Automatic enrich policy re-execution relies on Elasticsearch Watcher, which the free Basic license does not include. The view is still created and `REFRESH MATERIALIZED VIEW` still works (see below) | | **Eventual consistency** | Data is eventually consistent based on refresh frequency and delay |