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
Indexing
Composite range

Full Course

$
349
$399
USD, one-time fee
Just finished Aaron Francis' Mastering Postgres course. Highly recommend 🙂
Savannah

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

Discover how Postgres uses B-tree indexes to handle queries efficiently through left-to-right traversal. You'll learn how strict equality conditions on the left enable fast index access, while range conditions shift the query into scanning mode. By comparing different index orders—like "first last birth" versus "first birth last"—you'll see why index structure plays a key role in query performance.

Video Transcript

Left to right no skipping and stops at the first range.

This we haven't looked at stops at the first range yet.

In the previous video we looked at forming a leftmost prefix and as we discussed, Postgres does have the ability to go from a traversal to a scan if you do skip over a column. While it is not ideal, Postgres does cover you. The same thing applies when it comes to range conditions.

I've created two indexes here. First last birth, which is first name, last name, birthday, and first birth last, which is first name, birthday, last name. Kind of hard to say.

If we did select * from users where, and then we're gonna create this query where first name = Aaron and last name = Francis and birthday < 1989 02 14. If we run that, we'll see we don't get anybody back. Let's just change this to 12 31 because I do know when my birthday is. There we go. Now if we did explain which index is it using, it is using first last birth. So it selected first name, last name, birthday instead of selecting first name, birthday, last name. Why is that? Because that is the most efficient usage of a B-tree index.

Imagine that we're the database and we are tasked with this query, right? We can do a direct traversal like that animation we did earlier, we can do a direct traversal of the nodes looking for Aaron and Francis, right? So left, right, left, right, left right, boom, we got 'em, Aaron and Francis. Now we encounter a condition that says birthday is less than 12 31 of 1989. We don't know how to directly traverse that.

What we do is we take the Aaron Francis and we go to the very first matching node that says Aaron Francis. And we just start looking, we just start scanning all the way across until we reach 12 31 of 1989. Then that is our chunk of rows that we go grab from the heap. The first time that Postgres encounters a range condition, immediately it starts scanning the index, which is why you want your leftmost prefix to be a commonly used strict equality conditions. Then as you move to the right of your index, you can have less commonly used equality or your range conditions. Because if you skip over a column or you encounter a range condition, then it starts scanning, which is fine, but not as efficient as a direct traversal on equality.

Left to right, no skipping, stops at the first range.

In other databases, the index will not be used after the first range, or if you skip a column. Thanks to Postgres, it will continue to be used, but it's just not as efficient as we've seen here. It will pick the one that is most efficient, which in all cases, equality on the left, range scans on the right.