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 SQL
Returning keyword

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

Learn how to use the `RETURNING` feature in Postgres to retrieve updated values directly after performing upserts, inserts, updates, or deletes, eliminating the need for additional queries. This feature is incredibly useful for tracking changes, such as identifying deleted data or getting automatically generated IDs when inserting new records. It simplifies your workflow by providing essential details without extra effort, making it easier to work with updated entries in your application.

Video Transcript

Okay, we just finished upserts, which are very awesome. You gather up a query, you throw it over the wall and it either inserts or it updates, but if it updates, it'd be nice to kind of know what was the value, what's the new value, right?

Let's look at this upsert again. We're still here at hits:homepage and let's go ahead and just increment by one and we, we do our upsert, but the whole point of an upsert is like, let's do it all at once right? We're in this world where we have to do this insert and then or this upsert and then figure out, well what's the new value? Ah, it's 21. Great. I had to issue two queries. Well, that is not actually true. You can say returning. This is not just an upsert specific thing. We will talk about that in a second, but it is very helpful on upserts, you can say returning and in this case we're gonna say returning everything.

Just like a select *, return everything. Now look at that. We could just, you know, delete from kv, delete the whole table, and then do this and it's an insert, and we got the value of one back and now it's upsert, and we got the value of two back. Incredibly helpful. You don't have to do the whole thing, you could just do the value if you wanted. You could just do the key if you wanted. You could do key value, which in this case is the entire table. It's not that much different. But this is an incredibly powerful way to get back the values that were within Postgres's responsibility, right? We sent some stuff over, but we handed over the responsibility to Postgres to say, you figure out what to do with this, and after you figure it out, can you let me know? Can you give that back to me so that I am aware of it so that I don't have to turn around and issue another query?

This doesn't only work on upserts, it works on inserts, updates and deletes. A place that it can work on a delete here, as you can see, delete from kv, returning everything that was deleted. And that will show you all of the rows and columns that were deleted. That can be very, very helpful. Especially if you are doing delete from kv where expires_at is, yeah, is less than now or you know, current time or whatever. That might be very helpful to know like, "Hey, I'm gonna throw this query over the wall and say delete all this stuff." But I kind of wanna know, I kind of wanna know what was deleted without having to keep a diff myself and figure that out myself. This is also incredibly helpful when you're doing an insert and Postgres is in control of generating some of those columns. That could be of course a generated column. It could also be something where the default is like a current date or a current time, or just simply a primary key. Just simply a big end generated always as identity and you kind of want to insert it and then get that identity back. You can do that.

We're gonna do that here with insert into bookmarks and we'll insert the user ID and I believe it's called the URL values. I'm just gonna hope that a user ID of one exists and we'll say https, and then let's do tryhardstudios.com. It's a good website. If I insert that and I run that, it works. But what's the id? Maybe I wanted the id. If we do, again, if we do returning *, then I get the ID back, I get, well I get the whole row back and you'll see we still don't have that unique enforcement, so we can go back and delete duplicates using a CTE and a window function. But here I get the entire row back, which might be useful on the application side if I need to then, you know, potentially redirect them to a view page or an edit page for this resource. I need that unique ID so that I can show them that page after this has been created. Using returning * will give you back all of the values, including the ones that were in Postgres's control.