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)
Watch for free

Enter your email below to watch this video

Video thumbnail
Data Types
Storing money

Full Course

$
349
$399
USD, one-time fee
The course broke down complex subjects into easy-to-understand videos that I've referred back to multiple times since completing it. It strengthened my knowledge by filling gaps and expanding into new areas I hadn't known about—absolutely worth doing!
Lawrence
Lawrence

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 why using the `money` data type for storing monetary values in a database is not ideal. Instead, the video recommends two better options: storing money as an integer (e.g., cents) or using a `numeric` type for precise control over decimal places. Understanding the importance of precision and selecting the right method is crucial, especially when handling different currencies in your application.

Video Transcript

Let's talk about money. Everybody loves money. Everybody likes money. We need money, we need to store money in the database. There is a type specifically called money, I'm going to beg you to not use it to store money in the database.

Let me show you this type real fast. It is simply money. If you write this you do a create table with an item name as text a price as money, then we can insert into this table some values. We can insert literal floats, literal fractional values. Let's go ahead do a literal integer there. You can also insert a currency formatted string. If we were to run this then we can get rid of it we'll do select * from money. I think it's just money example. Look, all of them went in just fine. The one, I forget which one, I think it was headphones, we inserted as a string, totally fine. This one we inserted as an integer, totally fine. The rest of them we inserted as fractional values.

We inserted floats or whatever, decimals. Those all went in just fine. What's the problem with money? Well, let's do another one. If we were to insert another one here. In fact, let's stop inserting let's just start casting, that's a little bit easier to see. If we did select 199::money, okay, that's fine, 99, 99 99,876, just you lose precision, right.

If you are working with US dollars you don't care about fractional cents, maybe this is good. Depends on your application, of course, you're not going to lose precision for two decimal places. The second that you need fractional cents or you're working in a currency or maybe like a cryptocurrency where you need a bunch of fractional parts, your host, you're just straight outta luck.

Let's talk about another thing. Let's do that again. Let's select 1000 cast as money, okay? That comes back with a nice beautiful dollar sign a comma for the thousands separator. Let's say you're storing everything. You're storing everything in your database this way. US dollars, it all depends on this lc monetary setting. We have our monetary setting set to EN US.UTF-8. That is this setting right here. If we set this again, nothing is gonna change. All of this is going to work just fine. That is what's driving, we can do it over here. That's what's driving select * from money example. All those dollar signs, that is from lc monetary. If we were to simply change this to GB UTF-8, everything has changed to pounds. Great British pound sterling, I guess. Everything has changed two pounds except the actual values, right?

It looks like these were all stored as great British pounds, in fact they were stored as US dollars then we changed a setting it's totally hosed. This is very confusing 'cause it didn't actually do a conversion, like it didn't do a currency conversion, it just changed the sign. That's problem number one. We don't want that. , problem number two, maybe these are outta order. Problem number two was the two decimal precision. We potentially don't want that.

We are guaranteed that operations with money will be precise. That is good. If you think back to just a few videos ago, we have two other options for precision. Floating point is out because it's not precise let's talk about those two other options instead. The first option that you have is to store everything as an integer in the lowest unit that you need to store. If you're trying to store dollars cents not fractional cents, well then you can multiply the value times 100. You could do it like this. If you were to say, I'm trying to store the value $100.78 cents, you could say, alright, in fact, as an integer to get that to an integer, I just need to multiply it by 100 then I can store then we can cast this. Let's cast this to an int four. I can store that as cents. That is a totally legit way to store money.

Should you decide based on your application that you do in fact need to store fractions of a penny, well, you can do this you can say, what would that be? I think that's thousandth of a cent. There you go. The burden has shifted a little bit. The burden of understanding the data has shifted a little bit because you do have to remember on the way in, you need to make sure that it's easier to reason about talk about cents.

On the way in you convert it to cents on the way out, depending on what you're trying to display, you convert it back to dollars cents. This is very reasonable. You may not like it, which is totally fine. This is the way that Stripe does their API. If you get something back from Stripe, or I think if you send it to Stripe, you have to send it in the lowest unit of that currency. That makes it very, very easy because integers are fast, they're accurate, they're easy to pass around, nobody's gonna accidentally muck it up with a floating point operation. It's an integer. That is totally legit.

Obviously the other option that you have is to just simply store it as a numeric. This becomes, we'll just call this money. You can just store it as a numeric you can set the precision scale to whatever you want. Here we've left it completely unbounded. You could say I need 10 total digits two decimal place digits. That's fine. You could say I need four decimal place digits that would put you out to 0.7800. Depending on what the scale precision are that you need, you can declare numeric that way.

Remember, numeric is slower as we demonstrated in the comparison against floating point , that may not matter. The thing that you care most about when it comes to money is precision. You can store the lowest common unit, I wanna say lowest common denominator. You can store the lowest unit in the currency that you need or you can do it as a numeric set the precision scale however you need. When it comes to storing multiple currencies, my recommendation would be have a separate column that stores the currency next to the value. If everything is always going to be USD, I don't think I would store it.

The second that you add USD Canadian dollars great British pounds, that's when I would start putting a currency identifier next to the value. I would not rely on money at all for storage. If you wanted to, you could store it as a numeric then just for giggles, you could cast it into a money.

If you need that sort of like help on the presentation, that's totally fine. I have no problem with that. I just don't want you storing the money as money, store it as an integer or store it as a numeric.