Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions content/compatibility/sql_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ the [system table compatibility](../system_table) page.
| Table Partitioning | Partial | Only hash partitioning. Only at CREATE TABLE time |
| Foreign Data Wrappers | No | |
| Views | Yes | [Documentation](/docs/references/objects/views/) |
| Materialized Views | Yes | [Documentation](/docs/references/objects/materialized_views/) |
| Databases | Yes | [Documentation](/docs/references/objects/databases/) |
| Functions & Procedures | Yes | [Documentation](/docs/references/objects/functions/) <br> Also in cedar_script language |
| Custom Types | No | |
Expand Down
2 changes: 1 addition & 1 deletion content/compatibility/system_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
| [pg_ident_file_mappings](https://www.postgresql.org/docs/current/view-pg-ident-file-mappings.html) | 🟡 | Summarizes client user name mapping configuration. |
| [pg_indexes](https://www.postgresql.org/docs/current/view-pg-indexes.html) | 🟢 | Shows information about indexes. |
| [pg_locks](https://www.postgresql.org/docs/current/view-pg-locks.html) | 🟡 | Displays locks currently held or awaited. |
| [pg_matviews](https://www.postgresql.org/docs/current/view-pg-matviews.html) | 🟡 | Lists materialized views. |
| [pg_matviews](https://www.postgresql.org/docs/current/view-pg-matviews.html) | 🟢 | Lists materialized views. |

Check failure on line 163 in content/compatibility/system_table.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'pg_matviews'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'pg_matviews'?", "location": {"path": "content/compatibility/system_table.md", "range": {"start": {"line": 163, "column": 4}}}, "severity": "ERROR"}
| [pg_policies](https://www.postgresql.org/docs/current/view-pg-policies.html) | 🟡 | Displays information about policies. |
| [pg_prepared_statements](https://www.postgresql.org/docs/current/view-pg-prepared-statements.html) | 🟡 | Lists prepared statements. |
| [pg_prepared_xacts](https://www.postgresql.org/docs/current/view-pg-prepared-xacts.html) | 🔴 | Shows prepared transactions. |
Expand Down
3 changes: 2 additions & 1 deletion content/references/objects/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
* [Databases](./databases) — `CREATE`, `ALTER`, `DROP DATABASE`
* [Functions](./functions) — `CREATE`, `ALTER`, `DROP FUNCTION`/`PROCEDURE`, `DO`, `CALL`
* [Indexes](./indexes) — `CREATE`, `DROP INDEX`, index types, partial and expression indexes
* [Materialized Views](./materialized_views) — `CREATE`, `ALTER`, `DROP MATERIALIZED VIEW`, `REFRESH`

Check failure on line 13 in content/references/objects/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Google.EmDash] Don't put a space before or after a dash. Raw Output: {"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/references/objects/_index.md", "range": {"start": {"line": 13, "column": 45}}}, "severity": "ERROR"}
* [Policies](./policies) — `CREATE`, `ALTER`, `DROP POLICY`, row level security
* [Roles](./roles) — `CREATE`, `ALTER`, `DROP ROLE`, `GRANT`, `REVOKE`
* [Schemas](./schemas) — `CREATE`, `ALTER`, `DROP SCHEMA`, `search_path`
* [Tables](./tables) — `CREATE`, `ALTER`, `DROP TABLE`, constraints, partitioning
* [Views](./views) — `CREATE`, `DROP VIEW`, materialized views, `REFRESH`
* [Views](./views) — `CREATE`, `DROP VIEW`

Check failure on line 18 in content/references/objects/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Google.EmDash] Don't put a space before or after a dash. Raw Output: {"message": "[Google.EmDash] Don't put a space before or after a dash.", "location": {"path": "content/references/objects/_index.md", "range": {"start": {"line": 18, "column": 19}}}, "severity": "ERROR"}
272 changes: 272 additions & 0 deletions content/references/objects/materialized_views.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
---
title: "Reference: Materialized Views"
linkTitle: "Materialized Views"
weight: 35
---

A materialized view stores the result of a query as a table.
Unlike a regular [view](/docs/references/objects/views), CedarDB does not rerun the query when you read from a materialized view.
Instead, it returns the rows that it computed when the view was created or last refreshed.
Changes to the underlying tables become visible only after you run `REFRESH MATERIALIZED VIEW`.

```sql
CREATE TABLE trees (
id integer PRIMARY KEY,
species text NOT NULL,
height_m numeric NOT NULL
);
INSERT INTO trees VALUES (1, 'Oak', 12.4), (2, 'Oak', 20.1), (3, 'Birch', 9.0);

CREATE MATERIALIZED VIEW species_stats AS
SELECT species, count(*) AS tree_count, avg(height_m) AS avg_height_m
FROM trees
GROUP BY species;

SELECT * FROM species_stats ORDER BY species;
```

```text
species | tree_count | avg_height_m
---------+------------+--------------
Birch | 1 | 9.000000
Oak | 2 | 16.250000
```

## Using Materialized Views

Materialized views are useful for expensive queries whose results you read much more often than the underlying data changes.
Typical examples are aggregations for dashboards and reports, or pre-joined data for an application.

You can read from a materialized view like from any other table, join it with other tables and views, and create indexes on it.
You cannot change its contents with `INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, or `COPY FROM`.
The only way to change the contents is `REFRESH MATERIALIZED VIEW`.

After creating a materialized view, you can find it in the `pg_matviews` system view.
In `pg_class`, materialized views have the `relkind` value `m`.

## CREATE MATERIALIZED VIEW

`CREATE MATERIALIZED VIEW` defines a new materialized view and, by default, populates it with the result of the query.

```text
CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <name> [ ( <column_name> [, ...] ) ]
[ WITH ( <storage_option> = <value> [, ...] ) ]
AS <query>
[ WITH [ NO ] DATA ]
```

| Parameter | Description |
|-----------------------------|-------------------------------------------------------------------------------------------------------------|
| `IF NOT EXISTS` | Do not throw an error if a relation with the same name already exists. CedarDB does not run the query then. |
| `<name>` | The name of the materialized view, optionally schema-qualified. |
| `<column_name>` | Names for the columns of the view. Columns without a name in the list keep the name from the query. |
| `WITH (<strg_opt>)` | Storage options, as for [`CREATE TABLE`](/docs/references/objects/tables/#options), e.g., `server`. |

Check failure on line 63 in content/references/objects/materialized_views.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Google.Latin] Use 'for example' instead of 'e.g.'. Raw Output: {"message": "[Google.Latin] Use 'for example' instead of 'e.g.'.", "location": {"path": "content/references/objects/materialized_views.md", "range": {"start": {"line": 63, "column": 117}}}, "severity": "ERROR"}
| `<query>` | A `SELECT`, `VALUES`, or `TABLE` query that defines the contents. |
| `WITH DATA` | Run the query and populate the view. This is the default. |
| `WITH NO DATA` | Create the view without running the query. The view is unpopulated until you refresh it. |

Rename the output columns:

```sql
CREATE TABLE trees (id integer, species text, height_m numeric);

CREATE MATERIALIZED VIEW tallest (tree_species, max_height_m) AS
SELECT species, max(height_m) FROM trees GROUP BY species;
```

Store the contents of a materialized view on remote storage, such as S3, instead of local disk.
This works for materialized views just like for [tables](/docs/references/objects/tables/#options), and requires a server previously created with
[CREATE SERVER](/docs/references/advanced/createserver):

```sql
CREATE TABLE trees (id integer, species text, height_m numeric);

CREATE MATERIALIZED VIEW tree_heights WITH (server = remote_storage) AS
SELECT id, height_m FROM trees;
```

A materialized view can read from tables, views, and other materialized views.
It cannot read from temporary tables, neither directly nor through a view, because a materialized view outlives the session of a temporary table.

### Unpopulated materialized views

With `WITH NO DATA`, CedarDB only checks the query for errors and derives the columns, but does not run it.
Reading from an unpopulated materialized view is an error:

```sql
CREATE TABLE trees (id integer, species text);
INSERT INTO trees VALUES (1, 'Oak');

CREATE MATERIALIZED VIEW oaks AS
SELECT id FROM trees WHERE species = 'Oak'
WITH NO DATA;

SELECT * FROM oaks;
-- ERROR: materialized view "oaks" has not been populated

REFRESH MATERIALIZED VIEW oaks;
SELECT * FROM oaks;
-- id
-- ----
-- 1
```

The `ispopulated` column of `pg_matviews` and the `relispopulated` column of `pg_class` show whether a materialized view is populated.

### Indexes

You can create regular and unique [indexes](/docs/references/objects/indexes) on a materialized view.
CedarDB maintains them on every refresh.
If a refresh produces rows that violate a unique index, the refresh fails and the view keeps its previous contents:

```sql
CREATE TABLE trees (id integer, species text);
INSERT INTO trees VALUES (1, 'Oak'), (2, 'Birch');

CREATE MATERIALIZED VIEW tree_species AS SELECT id, species FROM trees;
CREATE UNIQUE INDEX ON tree_species (species);

INSERT INTO trees VALUES (3, 'Oak');
REFRESH MATERIALIZED VIEW tree_species;
-- ERROR: duplicate key value violates unique constraint "tree_species_species_key"
```

### Permissions

To create a materialized view, you need the `CREATE` privilege on the target schema and the `SELECT` privilege on all relations that the query reads.
The creating role becomes the owner of the view.

Other roles only need the `SELECT` privilege on the materialized view itself to read it.
They do not need any privileges on the underlying tables:

```sql
GRANT SELECT ON species_stats TO dashboard_reader;
```

## REFRESH MATERIALIZED VIEW

`REFRESH MATERIALIZED VIEW` replaces the contents of a materialized view with the current result of its query.

```text
REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <name>
[ WITH [ NO ] DATA ]
```

| Parameter | Description |
|----------------|--------------------------------------------------------------------------------------------------|
| `CONCURRENTLY` | Accepted for PostgreSQL compatibility. CedarDB never blocks readers during a refresh, see below. |
| `<name>` | The name of the materialized view, optionally schema-qualified. |
| `WITH DATA` | Rerun the query and populate the view with its result. This is the default. |
| `WITH NO DATA` | Discard the contents of the view and mark it as unpopulated. |

```sql
CREATE TABLE trees (id integer, species text);
INSERT INTO trees VALUES (1, 'Oak');

CREATE MATERIALIZED VIEW tree_count AS SELECT count(*) AS total FROM trees;

INSERT INTO trees VALUES (2, 'Birch');
SELECT total FROM tree_count;
-- 1

REFRESH MATERIALIZED VIEW tree_count;
SELECT total FROM tree_count;
-- 2
```

A refresh is transactional like any other write.
Concurrent transactions keep reading the previous contents until the refresh commits.
If you roll back the transaction, the view keeps its previous contents.

When a materialized view reads from another materialized view, refreshing the outer view uses the current contents of the inner one.
Refresh the views in dependency order, starting with the ones that only read from tables.

CedarDB does not refresh materialized views automatically.
To keep a view up to date, run `REFRESH MATERIALIZED VIEW` periodically or after loading new data.

### Permissions

Only the owner of a materialized view can refresh it.
The owner needs the `SELECT` privilege on all relations that the query reads.

## ALTER MATERIALIZED VIEW

`ALTER MATERIALIZED VIEW` changes the properties of an existing materialized view.
Specify `IF EXISTS` to not throw an error if the materialized view does not exist.

Rename a materialized view:

```sql
ALTER MATERIALIZED VIEW species_stats RENAME TO species_summary;
```

Rename a column of a materialized view.
This only renames the column of the view, not the column of the underlying table:

```sql
ALTER MATERIALIZED VIEW species_stats RENAME COLUMN tree_count TO number_of_trees;
```

Move a materialized view to another schema:

```sql
ALTER MATERIALIZED VIEW species_stats SET SCHEMA reporting;
```

Change the owner of a materialized view:

```sql
ALTER MATERIALIZED VIEW species_stats OWNER TO analyst;
```

`ALTER TABLE` can also rename a materialized view, move it to another schema, and change its owner.
It rejects changes that only apply to tables, such as adding columns or constraints, since the columns of a materialized view are defined by its query.

### Permissions

To alter a materialized view, you must be its owner.
Superusers can alter any materialized view.

## DROP MATERIALIZED VIEW

`DROP MATERIALIZED VIEW` removes a materialized view, its data, and its indexes.

```text
DROP MATERIALIZED VIEW [ IF EXISTS ] <name> [, ...] [ CASCADE | RESTRICT ]
```

```sql
DROP MATERIALIZED VIEW species_stats;
```

Do not throw an error if the materialized view does not exist:

```sql
DROP MATERIALIZED VIEW IF EXISTS species_stats;
```

If other views or materialized views read from the materialized view, the drop fails.
Use `CASCADE` to drop the dependent objects as well:

```sql
DROP MATERIALIZED VIEW species_stats CASCADE;
```

Use the right `DROP` statement for the object type: `DROP TABLE` and `DROP VIEW` do not drop materialized views.

### Permissions

To drop a materialized view, you must own it, own its schema, or be a superuser.

## Dependencies

A materialized view depends on all tables and views its query reads.
While the materialized view exists, CedarDB refuses to drop these relations or the columns the view reads, unless you use `CASCADE`.

You also cannot rename these relations or move them to another schema while the materialized view exists.

## PostgreSQL Differences

- `REFRESH MATERIALIZED VIEW` never blocks readers, as CedarDB uses snapshot isolation.
`CONCURRENTLY` is accepted as a synonym for a regular refresh, does not require a unique index, and can be combined with `WITH NO DATA`.
- CedarDB refuses to rename or move a table, view, or materialized view to another schema while a materialized view reads from it.
1 change: 1 addition & 0 deletions content/references/objects/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ weight: 30
You can use create view to define a *virtual* table, specified by a query.
When you reference the view, CedarDB reruns the query as if you would have specified the table as a subselect.
Views are similar to common table expressions (CTEs), but they survive connection and server restarts.
To store the result of a query instead of rerunning it, use a [materialized view](/docs/references/objects/materialized_views).

Usage example:

Expand Down
Loading