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
Advanced Data Types
Exclusion constraints

Full Course

$
349
$399
USD, one-time fee
Window function 😊. CTE with window function 😮. Hierarchical recursive CTE 🤯. Recommended all the way. Can’t wait to see the rest of the videos!
M Wildan Zulfikar
M Wildan Zulfikar

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

Exclusion constraints offer a powerful way to prevent overlapping reservations in a database—something unique constraints can't handle. By using GIST indexing along with the `btree_gist` extension, you can ensure that reservations don’t overlap within the same room while still allowing overlaps across different rooms. You'll also learn how to implement partial exclusions, such as permitting re-booking of canceled reservations, to create a more flexible and realistic booking system.

Video Transcript

We're gonna look at one more type of constraint in this module. We're gonna save foreign key constraints for indexing, it makes a little more sense over there. The exclusion constraint is very similar to a unique constraint, but with a little bit more power and a little bit more flexibility. You won't use them as often as a unique constraint, but when you need them, they are incredibly cool.

Let me show you. We have here a table for reservations, just a regular ID and then a room ID. And then the reservation period is a TS range. Where we're going to start is by saying that there can be no overlapping ranges in this table. That's not a great place to end, but it is a great place to start. Let's say we want to exclude using GIST, and GIST is a type of index that we will talk about later. And in here you put your criteria. How are we determining when we should exclude a row? We're saying reservation period, and then you pass through, you say width, and then you pass the operator in.

In this case we're gonna say, let's use the overlap operator. Functionally what we've done here is we've said when we are inserting a new row or updating a row, we need to check all the reservation periods and compare it with this double and operator to see if any of the periods overlap the one that we're trying to insert. If they do overlap, we need to exclude it. We need to say, nope, I'm sorry, you cannot insert that row. Let's create this table and then I'm just gonna paste this in here. Insert into reservations, room ID and reservation period. This reservation period is saying 0901 to 0903 and checkout is at noon and you have to be out by noon because the upper bound is not included. If we insert that, that looks good.

Let's just change this to 0204. The original reservation is 0103 and we're saying 02 to 04. That is an overlap. If we try to run that, we see, nope, you cannot do that. It conflicts with the existing key. That's pretty good. However, this is very, very wrong because if somebody tries to book a different room, it says, "Nope, you can't do that." That already overlaps with the existing key. We need to add another criteria to our exclusion constraint. That is we need to compare the room ID along with the reservation period because reservation periods can totally overlap, provided they're not overlapping in the same room.

Let's see if we can update this just a little bit. Let's drop table reservations. Let's get that outta there. What we need to do now is we need to say, "Room ID with strict equality," so room ID with strict equality, and then check the overlap of the reservation period. However, if we run that, you see we have a problem. Data type integer, which room ID is integer, has no default operator class for access method GIST. We're getting a little bit into the weeds here, but what this is saying is that this type of index has no operator for strict equality. We can get around that by enabling something called btree gist. We're gonna say create extension if not exists, btree gist. Then when we create that extension, now we can create that table. This adds a little bit of functionality to (indistinct) GIST index that allows strict equality, which is the btree. That's what a b tree is very good at. This is kind of combining those two things.

We will talk about knowing what extensions you have ready for you, knowing what extensions you have enabled, some common extensions. We will talk about all of that. But for now, we're just going to create the btree gist. With this table now created, let's get that out of the way and we're gonna try this again. Let's go back to room ID number one. We're gonna say September 2nd to September 4th. We can insert that. Room ID number two. Can they book the same period? They sure can, but for room ID number two, let's say somebody tries to book September 3rd to September 5th, and now if we run that, you'll see our key is two columns wide. It says it does conflict with the existing key, which in our case is an existing reservation. Now we have strictly enforced that for a given room, there cannot be a reservation overlap and it just never will enter the database. That gives me great comfort.

Let's look at one more example. We're working with the exact same table here, but I want to make it a little bit more realistic. Let's say that we have a booking status in here. We could make it an enum, but who has the time? I'm just gonna make it a text. Here's what I want to see if we can prove. We should be able, let's create the table. We should be able to insert a canceled reservation and then using the exact same range, insert a, let's just say confirmed reservation. We can't, right? Because we have this exclusion constraint up here that simply says, compare the room ID and the reservation period. If those overlap, then exclude it. That's not exactly what we were really looking for. If we do drop table reservations, we're not going to add anything in here. We're not gonna add anything into the conditions right here.

What we're gonna do is we're going to add it out here. We're basically going to make this a partial exclusion. And the partial part is we want where booking status does not equal canceled. If we create it this way, and we insert a canceled reservation, so we insert a canceled reservation and then using the exact same room ID, exact same timestamps, we can insert anything but canceled and we'll say confirmed. We can't insert confirmed twice because we're only excluding the ones where the booking status equals canceled. We have an exclusion that is much closer to reality when somebody cancels the room. That becomes available for us to book again using this what is called a predicate on our exclusion constraint.