Guide

How to query a JSON file with SQL

Most API exports come as JSON, and JSON is miserable to read by eye. Turning it into a table usually means writing a script. Real exports come in a few predictable shapes, though, so the conversion can happen the moment you open the file.

The shape that just works

An array of flat objects is already a table. Each object is a row and each key is a column. That’s what most "Export as JSON" buttons produce, and it needs no prep.

If a key only shows up in some objects, it still becomes a column. Rows without it get a null, like a blank cell in a spreadsheet.

SELECT customer_id, plan, monthly_revenue
FROM subscriptions
WHERE monthly_revenue > 100
ORDER BY monthly_revenue DESC;

Wrapper objects from APIs

API responses usually wrap the rows in an envelope with some metadata, like `{ "results": [...], "next": "..." }`. The array is your data. The rest is paging info.

When the object holds exactly one array, NoEgress unwraps it for you. A saved API response loads as the table you wanted, not as one row of metadata.

Nested values

A table cell holds a single value, so nested objects and arrays are kept as JSON text. A field like `tags: ["a","b"]` arrives as the string `["a","b"]`. You can still see it and query it.

For a quick question, plain string matching is often all you need.

SELECT id, tags
FROM events
WHERE tags LIKE '%urgent%';

Pull out a nested field

When string matching isn’t enough, DuckDB can parse that JSON text and extract the field you want.

SELECT
  id,
  json_extract_string(meta, '$.source')  AS source,
  json_extract_string(meta, '$.campaign') AS campaign
FROM events
WHERE json_extract_string(meta, '$.source') = 'email';

Check what actually loaded

JSON has no schema. The columns you get depend on which keys appear in the file. Profile the table before you query it, so you know which fields are filled in often enough to group by and which are mostly empty.

SELECT
  COUNT(*)                      AS rows,
  COUNT(campaign)               AS has_campaign,
  COUNT(DISTINCT source)        AS distinct_sources
FROM events;

Join JSON to a spreadsheet

Every file you open lands in the same session. A JSON export from one system joins straight to a CSV or Excel file from another. That’s usually the real job behind "how do I query this JSON?"

SELECT
  e.id,
  e.source,
  c.company_name
FROM events e
JOIN customers c
  ON e.customer_id = c.customer_id;

Frequently asked

Can I run SQL on a JSON file without converting it first?

Yes. An array of objects becomes a table as is. A wrapper object holding one array gets unwrapped automatically.

What happens to nested objects and arrays?

They’re kept as JSON text in the cell. You can match on them as strings or parse them with DuckDB’s json_extract functions.

What if my JSON is a single object rather than an array?

It loads as a one-row table. Handy for looking at a saved API response or a config file.

Try it on your own file

More guides

NoEgress vs Excel, Power Query, ChatGPT and DuckDB

How to use ChatGPT-style AI on sensitive spreadsheets without uploading them

An Excel alternative for files with millions of rows

How to query a CSV file with SQL

How to open large CSV files Excel can't handle

How to run SQL on an Excel spreadsheet

How to analyze confidential data without uploading it

How to check if a website uploads your file

How to compare two CSV files and find what changed

Data quality checks to run before trusting a dataset

DuckDB online: run DuckDB in your browser with nothing to install

How to ask a local AI model about your CSV or Excel data

How to remove duplicate rows from a CSV or Excel file

How to split a CSV or Excel file into multiple files by column

How to convert CSV to Excel, and Excel to CSV or Parquet

How to merge two CSV or Excel files