In the previous lesson we saw a way to make a partial index on the category_names table on only the English category names. What if we could make a mini-table of just those category names so we didn't have to add a WHERE language='en' on every single query we do? This what views are: they're lens we can query through that we can treat as if they were just normal tables.

CREATE VIEW
  english_category_names
AS
SELECT
  category_id, name, language
FROM
  category_names
WHERE
  language='en';

Now you can do:

SELECT * FROM english_category_names LIMIT 5;

A few things to note here:

  • We're querying this view as if it was a normal table. That's the power of views. You get to treat them as normal tables in your querying.
  • Imagine you have a contractor working on your app with you but you don't want to give them access to everything in a table that they need to work on. You can make a view and give them only access to that one view. One of the powerful aspects of views.
  • Views can utilized underlying indexes. You can't index a view itself but the data itself can be indexed.

Inserting into views

Some pretty cool we can do here with this view (but not all views) is we can actually insert into the view itself. Because it's simple enough that if we insert a category_id and name with the language it'll be smart enough to forward that on to the correct table.

INSERT INTO english_category_names (category_id, name, language) VALUES (2, 'Brian Holt biopic', 'it');

Note that we inserted an Italian language even though it's an English table. It's going to enforce that only English works here.

Views with joins

This is cool to have a filtered view on a table, but let's make it even more compelling. A view can be more-or-less any SELECT query. So we can put joins together so instead of wild joins we can just query a view.

Let's say you have a page on your app that you need to display actors & actresses, the roles they played, and the movies those roles were in and this was a really common querying pattern for your app. Your query would look like:

SELECT
  p.name AS person_name, c.role, m.name AS movie_name, p.id AS person_id, m.id AS movie_id
FROM
  people p

INNER JOIN
  casts c
ON
  p.id = c.person_id

INNER JOIN
  movies m
ON
  c.movie_id = m.id

WHERE
  c.role <> ''
LIMIT 5;

It's a bit of a mouthful from a quuerying perspective. So let's make it a view!

CREATE VIEW
  actors_roles_movies
AS
SELECT
  p.name AS person_name, c.role, m.name AS movie_name, p.id AS person_id, m.id AS movie_id
FROM
  people p

INNER JOIN
  casts c
ON
  p.id = c.person_id

INNER JOIN
  movies m
ON
  c.movie_id = m.id

WHERE
  c.role <> '';

Now you can do

SELECT * FROM actors_roles_movies LIMIT 20;

Much easier, right? And now we can do queries with this too! We can treat these views as if they were real tables when we do joins.

What actors & actresses tend to act in the same sorts of movies? We can know that by joining category names to movie keywords to movies to casts to people, right? Well, here we can make use of both of our views because we already have people to movies connected, and we have another view with just English category names already, so let's use both!

This is a very expensive query (most expensive so far, I got cost=293871.35..293871.40.) If you have a slow computer, uncomment the WHERE clause.

SELECT
  arm.person_name, ecn.name AS keyword, COUNT(*) as count
FROM
  actors_roles_movies arm

INNER JOIN
  movie_keywords mk
ON
  mk.movie_id = arm.movie_id

INNER JOIN
  english_category_names ecn
ON
  ecn.category_id = mk.category_id

-- WHERE arm.person_name = 'Julia Roberts'

GROUP BY
  arm.person_name, ecn.name

ORDER BY
  count DESC

LIMIT 20;

This is the best part about views! We get the full power of PostgreSQL behind this summarized views that live-derive from our existing data.