# Job postings


> 200,000 data job postings with full description text, for text classification, search, and AI function examples.

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](/sql-reference/motherduck-sql-reference/ai-functions/prompt/) 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`](/sql-reference/motherduck-sql-reference/ai-functions/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.

Embedded Dive: **Job features, one question set at a time**.
Dive ID: `5dfb1bb8-7a11-4372-8c2c-6ad1bafb0735`.

## 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:

#### Ten job postings

Database: `sample_data`

```sql
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:

#### Read one month with Hive partitioning

Database: `sample_data`

```sql
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:

#### Read a single month over HTTPS

Database: `sample_data`

```sql
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:

#### Load the full corpus into your own database

Database: `my_db`

```sql
CREATE TABLE my_db.job_postings AS
SELECT *
FROM read_parquet('s3://us-prd-motherduck-open-datasets/job_postings/parquet/year=*/month=*/jobs.parquet');
```

:::tip
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

#### Top locations by postings

Database: `sample_data`

```sql
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`](/sql-reference/motherduck-sql-reference/ai-functions/prompt-jev/) on this data.

### Hiring volume over time

#### Hiring volume over time

Database: `sample_data`

```sql
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:

#### Which postings mention a tool

Database: `sample_data`

```sql
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`](/key-tasks/ai-and-motherduck/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.


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

URL-encode query parameter values and send a GET request:

```text
GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fgetting-started%2Fsample-data-queries%2Fjob-postings%2F&page_title=Job%20Postings&text=<url-encoded user feedback, max 2000 characters>
```

Optionally append `&source=<url-encoded interface identifier>` such as `claude.ai` or `chatgpt`.

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed query parameters, and `429` when rate-limited.
