14 UUID and JSONB
Two of PostgreSQL’s most popular non-trivial types: uuid for opaque, globally unique identifiers, and jsonb for binary-encoded JSON that can be indexed and queried. UUIDs are the standard alternative to sequential integer IDs when you need IDs that are unguessable, generatable on the client, or globally unique across systems. jsonb is what makes PostgreSQL competitive with document stores for semi-structured data.
What you’ll learn
- Generating UUIDs with
gen_random_uuid() - Storing and querying JSON with
jsonb ->,->>,#>,#>>operators and what they return- The
@>containment operator (and how it uses GIN indexes) - The difference between
jsonandjsonb
Example
gen_random_uuid() produces a random version-4 UUID. It is built in from PostgreSQL 13 onward — no pgcrypto extension needed. PostgreSQL 17 has no built-in UUIDv7 generator; use an application/library or extension if you need time-ordered UUIDs. PostgreSQL 18 adds uuidv7(). A bigint identity column is another option.
For JSON, prefer jsonb over json. json stores the text exactly as you give it (including whitespace and key order); jsonb parses, normalizes, and stores in a binary form. jsonb is what you index, what you query efficiently, and what almost every application uses. Reach for plain json only when you need to preserve the input bytes verbatim (rare).
The -> operator extracts and returns jsonb. ->> extracts and returns text (stripping JSON quoting). #> and #>> take a path (an array of keys/indexes). @> (“contains”) tests whether the left value contains the right — it is the foundation of JSON queries.
Run the example
To run:
Common pitfalls
- Comparing
data -> 'name'to a string:data -> 'name' = '"Alice"'works (note the JSON-quoted string), butdata ->> 'name' = 'Alice'is what you usually want. - Forgetting an index: filtering on a
jsonbcolumn without a GIN index does a full table scan.CREATE INDEX ON docs_example USING gin (data);enables@>and similar queries to use the index. - Mutating jsonb in place:
UPDATE t SET data = data || '{"k": "v"}'::jsonbadds or replaces a top-level key;jsonb_setlets you change nested paths. - Schema drift inside
jsonb: it is flexible, but you still need to know what is in there. Document the expected shape, and validate at the application layer or with a CHECK constraint.
Index just the parts of JSON you query. Instead of indexing the whole data column with GIN, index a single path: CREATE INDEX ON docs_example ((data ->> 'name'));. This is a B-tree expression index — small, fast, and exact.
Use ->> to get the name as text: SELECT '{"name": "Alice"}'::jsonb ->> 'name';. Insert a row with nested data and query a nested field with #>>. Then add a GIN index on data and use @> for containment lookups.
Downloads
SQL file: uuid-jsonb.sql · All lesson files · Setup guide