Skip to content

Your first registration

Assume you have this materialised view:

CREATE MATERIALIZED VIEW reporting.daily_revenue AS
SELECT
    o.id                        AS order_id,
    o.customer_id,
    o.created_at::date          AS order_date,
    count(l.id)                 AS line_count,
    sum(l.unit_price * l.qty)   AS total
FROM  sales.orders o
      INNER JOIN sales.lines l
      ON o.id = l.order_id
WHERE o.status = 'PAID'
GROUP BY 1;

-- Required for concurrent refresh
CREATE UNIQUE INDEX ON reporting.daily_revenue (day);

You don't need to populate it first — register_mv() does that automatically when the view has no data yet (p_populate_if_empty defaults to true; see section 9):

SELECT pgauto_mv.register_mv(
    p_mv_name      => 'daily_revenue',
    p_mv_schema    => 'reporting',
    p_watch_tables => ARRAY[
        pgauto_mv.mv_watch('sales.orders', 'UPDATE')
    ]
);

If you'd rather control the first refresh yourself (for example, a bulk-load workflow — see section 8), pass p_populate_if_empty => false and call refresh_materialised_view_now() when you're ready.

pgauto_mv.mv_watch('sales.orders') watches the UPDATE event on the orders table. To watch all events:

pgauto_mv.mv_watch('sales.orders')

register_mv() returns a UUID — the auto_mv_id. This is the unique identifier for this registration. You will see it in the audit log and refresh history tables. Make a note of it or find it again with get_mv_status().

What happens next:

From this moment, every UPDATE on sales.orders triggers an automatic refresh of reporting.daily_revenue. Your application code changes nothing — writes to sales.orders work exactly as before, and the view refreshes in the background without affecting your transaction's performance.

If you later rename the view with ALTER MATERIALIZED VIEW ... RENAME TO, pg_auto_mv finds it automatically and updates its records. You do not need to re-register.