Views in Postgres


Quoting the great book PostgreSQL Up and Running, a view is nothing more than a query permanently stored in the database. Views have been evolving a lot in the latest versions of PostgreSQL and deserve some study to be well used.

The simplest use of a view can be something like:

CREATE OR REPLACE VIEW logs_2015 AS
SELECT value, year FROM logs where year = 2015;

This view uses only a single table, but we can also create views that uses more than one table:

CREATE OR REPLACE VIEW author_books AS
SELECT a.name, b.name, b.released_at
FROM authors AS a INNER JOIN books AS b
ON a.id = b.author_id;

Materialized Views

Since version 9.3 we can use a feature called MATERIALIZED VIEW. It’s the same as a normal view, but the data is cached when the view is created. It’s pretty useful when a commonly used query takes a long time to run and we don’t need real time data. We can also create indexes for materialized views, how amazing is that?

To update a MATERIALIZED VIEW we need to use the command REFRESH MATERIALIZED VIEW view, this will block queries while the refresh is running. In version 9.4 we have the option CONCURRENTLY that allows materialized views to be queried even during refresh, but there are some restrictions as can be seen here and the refresh takes longer.

Let’s see an example:

CREATE MATERIALIZED VIEW logs_2015 AS
SELECT value, year FROM logs where year = 2015;

Note that in this case we don’t have the CREATE OR REPLACE option. This happens because materialized views must be dropped if they already exist.

The PostgreSQL Documentation has a great section about materialized views.

INSERT, UPDATE and DELETE operations

Pior to version 9.3, these operations were only supported using RULES or TRIGGERS. From version 9.3 onwards these operations are all permitted in a view that references a single table, but we can only insert new data if our view has the primary key or the primary key is auto incremented.

In a view that references multiple tables we need to use triggers to achieve these operations. Explaining triggers would be a post on its own, but to start the documentation is a pretty good place.

One thing to keep an eye on is that your updates can place your records outside of your view. So, if we have a view that filters our records by the year of 2015 and we update these records to the year of 2016, our view will be empty after the operation. In version 9.4 we have the option WITH CHECK OPTION that blocks these kind of updates.

CREATE OR REPLACE VIEW logs_2015 AS
SELECT value, year FROM logs where year = 2015
WITH CHECK OPTION;

If we now try the following:

UPDATE logs_2015 SET year = 2016 WHERE value = 30;
ERROR:  new row for relation "logs_2015" violates
        check constraint "logs_2015_year_check"
DETAIL:  Failing row contains (15, 30, 2016).

We can see that we get an error, because our update would put the record outside the scope of our view.

Dropping a view

It’s really easy to drop a view, all you really need to do is:

DROP VIEW view;

There are some extra options that can be found in the documentation.

TL;DR

Views are great to simplify working with data, to structure and restrict access to it. It’s also good for caching complex queries and ensuring that the proper queries are running. Consider using one of them in your next project if you find a similar use case.