# Amazon Athena
> Query the same S3 data you query with Amazon Athena from MotherDuck by attaching the AWS Glue Data Catalog, reading files directly, or using Athena UNLOAD.
Athena uses the AWS Glue Data Catalog for table definitions. Because the data lives in S3 rather than inside Athena, MotherDuck reads it directly: you point MotherDuck at the catalog or at the files, not at Athena itself.

## Attach the Glue Data Catalog

For Iceberg tables registered in Glue, attach the catalog as a MotherDuck database. Table definitions stay in Glue, so tables added by Athena or a crawler show up in MotherDuck without extra setup:

```sql
CREATE SECRET glue_secret IN MOTHERDUCK (
    TYPE S3,
    KEY_ID '<aws_access_key_id>',
    SECRET '<aws_secret_access_key>',
    REGION '<aws_region>'
);

CREATE DATABASE my_glue_catalog (
    TYPE ICEBERG,
    endpoint_type 'glue',
    warehouse '<aws_account_id>',
    "secret" glue_secret,
    default_schema '<glue_database_name>'
);

SELECT * FROM my_glue_catalog.<glue_database_name>.orders LIMIT 10;
```

The IAM principal needs the Glue read actions plus `s3:GetObject` on the table locations. If your S3 locations are governed by AWS Lake Formation, extra setup applies. See [AWS Glue in the Apache Iceberg page](/integrations/file-formats/apache-iceberg#aws-glue) for both.

## Read the S3 files directly

For plain Parquet, CSV, or JSON prefixes, skip the catalog and read the files. Use `hive_partitioning` when the prefix encodes partition columns the way Athena expects:

```sql
CREATE SECRET my_s3_secret IN MOTHERDUCK (
    TYPE S3,
    KEY_ID '<aws_access_key_id>',
    SECRET '<aws_secret_access_key>',
    REGION '<aws_region>'
);

SELECT event_date, COUNT(*)
FROM read_parquet(
    's3://my-bucket/events/**/*.parquet',
    hive_partitioning = true
)
WHERE event_date >= DATE '2026-01-01'
GROUP BY ALL;
```

Reading the files directly means the partition pruning and predicate pushdown are DuckDB's, not Athena's. See [S3 import best practices](/key-tasks/cloud-storage/s3-import-best-practices) for file layout and sizing guidance.

## Export an Athena query result

When the source table isn't a format MotherDuck reads, or the query does Athena-specific work you don't want to rewrite yet, let Athena write the result out as Parquet:

```sql
UNLOAD (SELECT * FROM my_database.orders WHERE order_date >= DATE '2026-01-01')
TO 's3://my-bucket/athena-unload/orders/'
WITH (format = 'PARQUET', compression = 'SNAPPY');
```

Then load the result in MotherDuck:

```sql
CREATE TABLE orders AS
SELECT * FROM read_parquet('s3://my-bucket/athena-unload/orders/*.parquet');
```

`UNLOAD` writes to an empty prefix, so use a fresh path per run or clear the prefix first.

## Load on a schedule with a Flight

A [Flight](/concepts/flights) is Python that MotherDuck schedules and runs next to your data. Use one instead of running the load by hand when the refresh should repeat on a cron, retry on a transient failure, and keep a run history.

Unlike an extension-based source, there is nothing to attach here: the `read_parquet` above already runs server-side on MotherDuck compute. So the Flight schedules the load rather than moving the rows itself:

```python
import duckdb

def main():
    con = duckdb.connect("md:")
    con.execute("CREATE DATABASE IF NOT EXISTS athena_ingest")
    con.execute(
        "CREATE OR REPLACE TABLE athena_ingest.main.orders AS "
        "SELECT * FROM read_parquet('s3://my-bucket/athena-unload/orders/*.parquet')"
    )

if __name__ == "__main__":
    main()
```

The bucket credentials are read by MotherDuck's own compute, so they belong in the `TYPE S3` secret created above rather than inline in the Flight's code. A Flight needs a [Flight secret](/sql-reference/motherduck-sql-reference/create-secret#flight-secrets) only for credentials its Python reads directly.

Create the Flight with [`MD_CREATE_FLIGHT`](/sql-reference/motherduck-sql-reference/flights/md-create-flight), passing the Python above as `source_code` and a `schedule_cron` for the cadence you want. [Ingest partitioned S3 Parquet on a schedule](/cookbook/flight-scheduled-s3-ingest) is a fuller version that refreshes only the partition that changed instead of replacing the whole table. If you attach the Glue catalog instead of reading files, a Flight isn't needed for freshness: new Glue tables show up in MotherDuck without a copy.

## Things to know

- **There is no `athena` extension.** MotherDuck doesn't connect to the Athena query API. Every route above goes to the catalog or the storage instead, which is also why there's no per-query Athena scan cost involved.
- **Glue is the shared surface.** If Athena and MotherDuck should see the same tables, keep table definitions in Glue and let both engines read them, rather than maintaining two catalogs.
- **Workgroup output location.** Athena's own query results in the workgroup output location are CSV with a metadata sidecar file. Read those with `read_csv` and a filename filter, or use `UNLOAD` to get clean Parquet instead.

## Related content

- [Apache Iceberg](/integrations/file-formats/apache-iceberg#aws-glue)
- [AWS Glue](/integrations/ingestion/aws-glue)
- [Amazon S3](/integrations/cloud-storage/amazon-s3)
- [S3 import best practices](/key-tasks/cloud-storage/s3-import-best-practices)
- [Athena `UNLOAD` documentation](https://docs.aws.amazon.com/athena/latest/ug/unload.html)


---

## 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=%2Fintegrations%2Fdatabases%2Famazon-athena%2F&page_title=Amazon%20Athena&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.
