Materialized Views

Materialized views; any query change emits DROP MATERIALIZED VIEW then CREATE MATERIALIZED VIEW.

Basic materialized view

SCHEMA analytics {
    MATERIALIZED VIEW daily_revenue AS
        SELECT
            date_trunc('day', created_at) AS day,
            SUM(total_amount)             AS revenue,
            COUNT(*)                      AS order_count
        FROM orders
        WHERE status = 'completed'
        GROUP BY 1;
}
-- emits
CREATE MATERIALIZED VIEW "analytics"."daily_revenue" AS
    SELECT
        date_trunc('day', created_at) AS day,
        SUM(total_amount)             AS revenue,
        COUNT(*)                      AS order_count
    FROM orders
    WHERE status = 'completed'
    GROUP BY 1;

With storage options and WITH NO DATA

MATERIALIZED VIEW product_stats
WITH (fillfactor = 90)
TABLESPACE analytics_space AS
    SELECT product_id, COUNT(*) AS purchases, AVG(price) AS avg_price
    FROM order_items
    GROUP BY product_id
WITH NO DATA {
    INDEXES { idx_product_stats_id (product_id); }
    GRANTS  { SELECT TO app_readonly; }
}
-- emits
CREATE MATERIALIZED VIEW "public"."product_stats"
    WITH (fillfactor = 90)
    TABLESPACE "analytics_space" AS
    SELECT product_id, COUNT(*) AS purchases, AVG(price) AS avg_price
    FROM order_items
    GROUP BY product_id
WITH NO DATA;

GRANT SELECT ON "public"."product_stats" TO "app_readonly";

CREATE INDEX IF NOT EXISTS "idx_product_stats_id"
    ON "public"."product_stats" ("product_id");

Diffing behaviour

Any change to a materialized view’s query (beyond whitespace, which is normalized away before comparison) emits DROP MATERIALIZED VIEW then CREATE MATERIALIZED VIEW — always DESTRUCTIVE. Unlike regular views, there is no CREATE OR REPLACE MATERIALIZED VIEW.