Job postings
200,000 job postings for data roles, each with the full description text. Three short columns to group and filter on, one long text column to classify. That makes it the dataset the AI function examples in these docs run against.
Explore the data
This Dive pages through the postings and answers typed questions about each one with prompt_jev — is this a data role, which role family, what seniority, does it state a salary, does it manage people. Ask your own yes/no question in the box and the page re-sorts by how well each posting matches.
About the dataset
| Postings | 199,999 |
| Listed between | 2025-01-01 and 2026-08-23 |
| Locations | 3,129 |
| Average description length | 4,316 characters |
| Files | 20 Parquet files, one per listing month, ZSTD, 231 MB total |
Every posting is for a data role. Titles were filtered on terms like analyst, analytics, data, BI, ML, engineer, and the common tool names, so the corpus is dense in one domain rather than spread thin across every job on the market. Duplicate postings are removed: one row per posting, keyed on job_id.
The dataset keeps the posting, not the poster. There is no employer column, no URLs, no applicant or engagement counts, and no company profile fields — only the five columns below. Descriptions are unedited, so many of them still name the employer in the body text.
How to query the dataset
The files are public and need no credentials. Use the s3:// path to read the whole corpus, because a glob needs a directory listing and plain HTTPS has none:
SELECT listed_date, title, location
FROM read_parquet('s3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet')
ORDER BY listed_date DESC, job_id
LIMIT 10;The files are laid out in Hive-style folders, so a year and month filter reads only the months it needs:
SELECT
count(*)
FROM
read_parquet(
's3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet',
hive_partitioning = TRUE
)
WHERE
year = 2026
AND month = '03';A single month is one file, about 10 MB, and that one you can read straight over HTTPS:
SELECT * FROM 'https://us.data.motherduck.com/job_postings/parquet/year=2026/month=03/jobs.parquet';
To query it repeatedly, load it into your own database once. The description column is the expensive part of every scan:
CREATE TABLE my_db.job_postings AS
SELECT *
FROM read_parquet('s3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet');Parquet reads only the columns a query names. SELECT title, location over all 20 files touches a few megabytes; adding description touches all 231 MB. Leave the description out until a query needs it.
Example queries
Where the roles are
SELECT location, count(*) AS postings
FROM read_parquet('s3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet')
GROUP BY location
ORDER BY postings DESC
LIMIT 8;location is free text as the employer typed it, so London appears twice under two spellings. Normalizing it is its own exercise, and a reasonable first use of prompt_jev on this data.
Hiring volume over time
SELECT
date_trunc('quarter', listed_date) AS quarter,
count(*) AS postings
FROM read_parquet('s3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet')
GROUP BY quarter
ORDER BY quarter;The last quarter is partial: collection stops on 2026-08-23.
Which postings mention a tool
The description is free text, so a keyword search finds mentions but not meaning. ilike catches "we use dbt" and "no dbt experience needed" alike:
SELECT
count(*) FILTER (description ILIKE '%dbt%') AS mentions_dbt,
count(*) FILTER (description ILIKE '%airflow%') AS mentions_airflow,
count(*) FILTER (description ILIKE '%duckdb%') AS mentions_duckdb,
count(*) AS postings
FROM read_parquet('s3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet');To separate a requirement from a passing mention, ask the model instead of the string. See Classify text with prompt_jev.
Schema
job_postings
| column_name | column_type | description |
|---|---|---|
job_id | VARCHAR | Unique per posting. Use it as the join key for anything you derive. |
listed_date | DATE | The day the posting went up. |
title | VARCHAR | Job title as posted. |
location | VARCHAR | Location as posted, free text: "London Area, United Kingdom", "Menlo Park, CA". |
description | VARCHAR | The full posting body, 4,316 characters on average. |
year and month come from the folder names when you read with hive_partitioning = true. month is a zero-padded VARCHAR, so compare it as month = '03'.
Rows have no guaranteed order. Sort with ORDER BY listed_date DESC, job_id whenever order matters.