SQL's Complex Types
Concept. SQL extends beyond INT and TEXT. A relational engine can store and query geographic points, documents, arrays, DNA strings, and even LLM calls through the same SELECT / FROM / WHERE.
Intuition. Spotify can filter users within 5 km of a venue, store per-order JSON in an orders table, and tag songs as "energetic" with an LLM. In Postgres or BigQuery, each task becomes a function call inside an ordinary query: ST_DWITHIN(...), a JSONB lookup, AI.CLASSIFY(...).
Course note: good to know for interviews; exact syntax is optional for tests.
Figure 1. Domain-specific types plug into the same SELECT / FROM / WHERE / ORDER BY shape through functions. A query can place a distance predicate in WHERE, extract a JSON path in SELECT, or call an LLM as a computed column.
JSONB Before NoSQL
A little JSON in your data is not a reason to stand up a NoSQL cluster. One PostgreSQL node with a JSONB column gives you a flexible document schema and ACID transactions and joins, which carries an MVP well past the point most teams think they need a custom NoSQL database.
-- One Postgres node with JSONB: ACID and joins from the relational side,
-- a flexible document schema from the JSONB column.
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
items JSONB, -- Flexible per-order document
created_at TIMESTAMP
);
Modern Postgres added JSONB, vectors, and geo-spatial types fast enough that it absorbed much of the demand that drove specialized document and vector databases in the 2024 to 2025 AI wave.
Geographic: Finding Things Nearby
-- Spotify users within 5 km of Stanford (BigQuery)
SELECT
u.name,
u.email,
ST_DISTANCE(u.location, ST_GEOGPOINT(-122.1697, 37.4275)) AS distance_meters
FROM Users u
WHERE ST_DWITHIN(
u.location,
ST_GEOGPOINT(-122.1697, 37.4275), -- longitude, latitude
5000 -- meters
)
ORDER BY distance_meters;
ST_DWITHIN is a WHERE predicate like any other; the engine handles the spherical geometry.
Generative AI: Semantic Understanding
LLM functions turn a column of free text into a column of structured output, in the same query that filters and sorts it.
-- Tag, summarize, and translate songs with an LLM (BigQuery)
SELECT
title,
artist,
AI.CLASSIFY(lyrics, ["Happy", "Melancholic", "Energetic", "Chill"]) AS vibe,
AI.GENERATE("Summarize the emotional arc IN one sentence", lyrics) AS ai_summary,
AI.TRANSLATE(title, "Spanish") AS title_es
FROM Songs
WHERE AI.IF("The song IS about a summer romance", lyrics)
AND play_count > 100000;
User-Defined Functions: Custom Logic
A UDF puts your business logic inside SQL: define it once, call it like any built-in. Use SQL for simple math, JavaScript when SQL alone is not expressive enough.
Define Once
-- SQL UDF: simple, fast, inlined
CREATE TEMP FUNCTION viralScore(plays INT64, shares INT64)
RETURNS FLOAT64
AS (plays * 0.3 + shares * 10);
-- JavaScript UDF: when SQL alone falls short
CREATE TEMP FUNCTION normalizeTitle(t STRING)
RETURNS STRING
LANGUAGE js AS """
return t.toLowerCase().trim();
""";
Use Anywhere
SELECT
normalizeTitle(s.title) AS title,
s.artist,
viralScore(s.play_count, s.share_count) AS viral_potential
FROM Songs s
WHERE s.release_year = 2024
AND s.play_count > 1000
ORDER BY viral_potential DESC
LIMIT 100;
A UDF (viralScore) runs per row; a UDAF runs over a group for custom aggregations like a weighted average. Either way, you define it once and reuse it across queries.
Try It Yourself: BigQuery Public Datasets
Optional. If you want to run something now, these work today on the free tier. Skip without losing the thread.
-- Cafes named Starbucks within 10 km of downtown San Francisco
SELECT name, address, geom
FROM `bigquery-public-data.geo_openstreetmap.planet_features`
WHERE feature_type = 'amenity'
AND tags['amenity'] = 'cafe'
AND tags['name'] LIKE '%Starbucks%'
AND ST_DWITHIN(geom, ST_GEOGPOINT(-122.4194, 37.7749), 10000);
-- COVID variants with more than 10 mutations
SELECT variant_name, country, date, mutation_count
FROM `bigquery-public-data.covid19_genome_sequence.variants`
WHERE mutation_count > 10
ORDER BY date DESC;
The Pattern Holds
SQL has absorbed new data types every decade, and the query shape has not changed.
1970s: numbers and text
1990s: + dates and binary
2000s: + XML and spatial
2010s: + JSON and arrays
2020s: + vectors, geo, and LLM calls
Each decade added functions, not a new language. The SELECT / FROM / WHERE you know already reaches the next data type.