Mastering Postgres
Introduction
Introduction to the course
Overview of course structure
Postgres vs. everyone
The psql CLI
Data Types
Introduction to schema
Integers
Numeric
Floating point
Storing money
NaNs and infinity
Casting types
Characters types
Check constraints
Domain types
Charsets and collations
Binary data
UUIDs
Boolean
Enums
Timestamps
Timezones
Dates and times
Advanced Data Types
Intervals
Serial type
Sequences
Identity
Network and mac addresses
JSON
Arrays
Generated columns
Text search types
Bit string
Ranges
Composite types
Nulls
Unique constraints
Exclusion constraints
Foreign key constraints
Indexing
Introduction to indexes
Heaps and CTIDs
B-Tree overview
Primary keys vs. secondary indexes
Primary key types
Where to add indexes
Index selectivity
Composite indexes
Composite range
Combining multiple indexes
Covering indexes
Partial indexes
Index ordering
Ordering nulls in indexes
Advanced Indexing
Functional indexes
Duplicate indexes
Hash indexes
Naming indexes
Understanding Query Plans
Introduction to explain
Explain structure
Scan nodes
Costs and rows
Explain analyze
Generating Results
Introduction to queries
Cross joins
Inner joins
Outer joins
Subqueries
Lateral joins
ROWS FROM
Filling gaps in sequences
Subquery elimination
Combining queries
Set generating functions
Indexing joins
Advanced SQL
Introduction to advanced SQL
Grouping
Grouping sets, rollups, cubes
Window functions
CTEs
CTEs with window functions
Recursive CTE
Hierarchical recursive CTE
Handling nulls
Row value syntax
Views
Materialized views
Removing duplicate rows
Upsert
Returning keyword
COALESCE + generated column
Full Text Search
Introduction to full text search
Searching with LIKE
Vectors, queries, and ranks
Websearch
Ranking
Indexing full text search
Highlighting
Advanced JSON
Intro to JSON
JSON vs JSONB
Validating JSON
Creating JSON objects + arrays
JSON extraction
JSON containment
JSON existence
JSON recordset
Updating JSON
Indexing JSON parts
GIN index
Vectors (pgvector)
Intro to pgvector
Vector embedding columns
Find related articles
Upsert vector embedding
Semantic search
Other operators
Vector indexes
Outro
Thank you
Bonus interviews
Heroku's glory days & Postgres vs the world (with Craig Kerstiens)
Creating a Postgres platform with Monica & Tudor from Xata.io
Bootstrapping an email service provider (with Jesse Hanley)
Locked video

Please purchase the course to watch this video.

Video thumbnail
Understanding Query Plans
Explain analyze

Full Course

$
349
$399
USD, one-time fee
Aaron is a natural teacher and this course is the best introduction to Postgres I have come across. Lessons are easy to follow and he recommends some great tools for working with Postgres.
Joel Drumgoole
Joel Drumgoole

PostgreSQL database platform

Shorten dev cycles with branching and zero-downtime schema migrations.

Test Data

I've made my test data available for you to use and follow along.

Download
or
Use on Xata

Summary

Using `EXPLAIN ANALYZE` in Postgres provides detailed insights into how a query actually performs, including planning time, execution time, and row counts. Unlike `EXPLAIN` alone, it runs the query, so it's important to be cautious—especially if the query modifies data. This tool is invaluable for diagnosing performance issues, but should be used carefully in write-heavy or production environments.

Video Transcript

All of these results that we've been getting back from EXPLAIN are estimates. They're good estimates, but they're estimates nonetheless. If you want an actual reporting of how the query ran, you can add the word analyze to your EXPLAIN.

With a huge red flashing caveat, it's going to run the query.

It's going to run the query even if it discards the results. It is going to run the query even if it is an update, delete and insert, it is going to run the query. You have been warned, be very careful.

Here's our estimated one that just shows the cost estimates and if you want to actually run the query, I'm gonna keep warning you, you can put analyze there. You don't have to put it in parentheses, but you can. That's fine, we'll just run it that way. You'll see we get a lot more information. We get a planning and execution time, which is very helpful. We get a little bit more detail, which we're not gonna go into right now, but then we also get this extra set of parentheses out here. We get the actual time of 28.8 and then 42 there, which is again, startup time and total time. We get the actual rows, which we can compare and see. It was pretty close, if not exactly right, still pretty close. And then some loops.

Now, we'll talk about loops in a second, but the cost and the actual time, those are fundamentally different units. The cost is Postgres arbitrary cost units, the time is milliseconds. If you're looking at those things and you're thinking, "Ah, those two should line up." And if they don't, then something's terribly wrong. They're just different units. They're not gonna line up. If that annoys you, you can turn them off. The way that you would do that is open the parentheses here and then you could do a comma costs off, and that will just show you the actual time.

The one thing that we have remaining here is the loops. Each node could be run multiple times. Depending on where it is in the query, what else is going on, it could be run once for every row in the result set. It could be run so many times. What you'll end up seeing here is the average. If you see a total cost of 19.604, that is a total cost per iteration based on an average. If you see 19 milliseconds in here, but it ran a million times, it's a pretty good offender. You might wanna check into that and see if we can get that down. That's what that loops means there.

EXLAIN ANALYZE, hugely helpful tool when you have exhausted all of the EXPLAINs. If you can't solve it with an EXPLAIN, you might reach for an EXPLAIN ANALYZE with the giant caveat that it's going to actually run your query, so be very careful out there.