High Performance
SQLite
Introduction
Introduction to this course
Introduction to SQLite
SQLite is safe
It's just a file
Running SQLite locally
Good uses for SQLite
SQLite's limitations
The documentation
SQLite Internals
SQLite's structure
SQLite's file format
Dot commands
Pragmas
Virtual tables
Schema
Flexible types
Types
Strict types
Dates
Booleans
Floating point
Rowid tables
Auto increments
Without rowid
Generated columns
Optimizing SQLite
Locking
Rollback mode
WAL mode
WAL vs Journal benchmarks
Busy timeout
Transaction modes
Vacuum
Analyze & Optimize
Suggested pragmas
Faster inserts
Indexes
Introduction to indexes
B+ trees
Primary, secondary, and clustered indexes
Without rowid tables
Benchmarking without rowid tables
Imposter tables
Primary key data types
Where to add indexes
Index selectivity
Composite indexes
Composite ordering
Covering indexes
Partial indexes
Indexes on expressions
Automatic indexes
Duplicate indexes
Indexing joins
Advanced SQL
Explain
Index obfuscation
Joins
Subqueries
Unions
CTEs
Recursive CTEs
Window functions
Dealing with NULLs
Row value syntax
Indexed sorting
Upserts
Returning
Aggregates
Triggers
Operating SQLite
Altering schema
Altering schema with tools
Multi-database
Multi-tenancy
Backups
Exports
How to corrupt SQLite
Advanced JSON
Intro to JSON
JSON vs. JSONB
Argument types: path, value, JSON
JSON5
Valid JSON
Creating JSON objects + arrays
JSON Extraction
Updating JSON
JSON aggregates
JSON table functions
Indexing JSON
Full Text Search
Creating FTS tables
Performing searches
Ranking results
Highlighting matches
Bonus Interviews
DHH discusses SQLite in Ruby on Rails
Distributed SQLite with Litestream and LiteFS
Offline-first, multiplayer SQLite
Production SQLite with Turso and libSQL
Migrating from Postgres to SQLite with Kent C. Dodds
Ruby on Rails with SQLite
Moving from Redis to SQLite with Mike Buckbee
Locked video

Please purchase the course to watch this video.

Video thumbnail
Advanced SQL
Returning

Full Course

$
179
$229
USD, one-time fee
This is by far the best course on SQLite out there, Aaron put a lot of effort into it to explain the ins and outs of this fantastic database. Highly recommend it!
Alexandru Vladutu
Alexandru Vladutu

SQLite for Production

Build with and integrate SQLite into your production applications.

Test Data

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

Download
or
Use on Turso

Summary

In this video, I explain how the "returning" keyword in SQLite can be used to get the updated or deleted data immediately after making changes with upserts, inserts, updates, or deletes. This is similar to how Postgres implements it and provides significant convenience by reducing the need for additional queries to fetch new values. The "returning" keyword is particularly useful when you want to retrieve auto-generated values or confirm the changes made to the database without relying on the order of returned results.

Video Transcript

We've just covered upserts which is a non standard SQL feature that SQLite implements based on the way that Postgres does it. And now we have returning which is also non standard sequel, very helpful and also implemented in a similar way as postgres. Let's take a look. In this last video, we did this thing here where we were selecting from key value and then when there is a conflict on key we did an update to increment the value. And, I mean it works, right?

It works and we get query 1, okay, but like what is the new value? You would potentially have to issue a second query to go back and get the new value which is now 20 unless you use the returning clause and you can say returning everything. So if you return everything, you get the row that was updated back which is incredibly helpful. If you want just the column, just any particular column, you could say, give me value, you could say, give me key, you could say, give me value as new val and that would work just as well. This is extremely incredibly helpful.

So with this zero zero zero, let's do delete from kv where key equals that guy returning everything. This shows you what was deleted. So you can issue, you can issue like a, what is the word that I'm looking for? What was the word? Speculative.

You could issue a speculative delete and then use this returning keyword to get back all of the rows that were returned. So now we can insert it again and you see that there was no conflict, so we inserted the right value and we got one back as the new value. This works with inserts, updates, and deletes. You must remember or you must be aware, that these returnings may not come back in the same order. So, how could we show this?

We can do something like we can do, we can do a second we can do a second insert here and let's make it this one. So we'll insert 1 and 1, otherwise, we'll increment. And if you do the returning, you're not necessarily guaranteed that this 36 are in this order. So you need you do need to be careful that the returning can come back in any arbitrary order. So don't count on it being the same order as whatever you've done over here, which is why it's usually good to return everything got our upsert, we've got got our upsert, we've got our returning, we've got our SQLite is incredibly good.

Returning can be helpful in other scenarios besides upserts. You can imagine inserting and immediately getting back the, perhaps, auto generated ID for that row. Inserting, a row that has either a SQLite random or SQLite date, time or something where SQLite is responsible for generating the value. And then once you insert it, you wanna get that immediately back with out issuing a new query. In that case, you would want to reach for the returning keyword.