# Snowflake
> Move data from Snowflake into MotherDuck with a Python job that copies tables over Arrow, by unloading Parquet to object storage, or by attaching an Iceberg catalog both engines can read.
Snowflake is a cloud data warehouse. There is no first-class DuckDB `snowflake` extension, so data moves between Snowflake and MotherDuck one of three ways: a Python job that pulls tables over Arrow, a Snowflake unload to object storage that MotherDuck reads back, or an Iceberg catalog that both engines can see.

Pick the route based on how often the data has to move and who owns the schedule.

## Copy tables over Arrow

For a repeatable, code-driven copy, use `snowflake-connector-python` to fetch a query result as an Arrow table, register it with a DuckDB connection, and write it into MotherDuck:

```python
import duckdb
import snowflake.connector

md = duckdb.connect("md:")
sf = snowflake.connector.connect(
    account="<account_identifier>",
    user="<user>",
    password="<password>",
    warehouse="<warehouse>",
    role="<role>",
)

cursor = sf.cursor()
cursor.execute("SELECT * FROM analytics.public.orders")
orders_arrow = cursor.fetch_arrow_all()

md.register("orders_arrow", orders_arrow)
md.sql("CREATE OR REPLACE TABLE my_db.main.orders AS SELECT * FROM orders_arrow")
```

`fetch_arrow_all()` needs `pyarrow` installed and materializes the whole result in memory, so chunk large tables by a date or ID range, or use `fetch_arrow_batches()` and insert batch by batch.

To run this on a schedule without managing infrastructure, wrap it in a [Flight](/concepts/flights). The [Snowflake ingest Flight recipe](/cookbook/flight-snowflake-ingest) does this in two phases: a `discover` phase that writes an editable inventory of source tables to a MotherDuck control table, and a `move` phase that copies the tables you flagged. It is built for keeping Snowflake as the source of truth while you build out MotherDuck alongside it.

## Unload Parquet to object storage

For large one-time loads and backfills, let Snowflake write the data out and have MotherDuck read the files. This keeps the transfer off your machine and lets MotherDuck's cloud compute do the reading.

In Snowflake, unload the table to your bucket as Parquet:

```sql
COPY INTO 's3://my-bucket/snowflake-unload/orders/'
FROM analytics.public.orders
STORAGE_INTEGRATION = my_s3_integration
FILE_FORMAT = (TYPE = PARQUET)
HEADER = TRUE
MAX_FILE_SIZE = 268435456;
```

In MotherDuck, store the bucket credentials in a secret and read the files:

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

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

For incremental follow-up loads, unload only the new rows and append them with the watermark patterns in [Data loading patterns](/key-tasks/loading-data-into-motherduck/loading-patterns).

## Share an Iceberg catalog

If your tables are Snowflake-managed Iceberg tables published through an Iceberg REST catalog, such as Snowflake Open Catalog, both engines can read the same tables without copying anything. Attach the catalog as a MotherDuck database:

```sql
CREATE SECRET my_catalog_secret IN MOTHERDUCK (
    TYPE ICEBERG,
    CLIENT_ID '<client_id>',
    CLIENT_SECRET '<client_secret>',
    OAUTH2_SERVER_URI '<oauth_token_endpoint>'
);

CREATE DATABASE my_lakehouse (
    TYPE ICEBERG,
    "secret" my_catalog_secret,
    endpoint '<catalog_endpoint>',
    warehouse '<warehouse_identifier>',
    default_schema '<schema_name>'
);
```

See [Apache Iceberg](/integrations/file-formats/apache-iceberg) for the full option list, authentication details, and write limitations.

## Use an ingestion tool

Ingestion tools that list Snowflake as a source can load into MotherDuck as a destination: [dlt](/integrations/ingestion/dlt), [Sling](/integrations/ingestion/sling), [Airbyte](/integrations/ingestion/airbyte), and [Fivetran](/integrations/ingestion/fivetran). Use one of these when you already run it and want Snowflake to be one source among several.

## Things to know

- **Identifier case.** Snowflake stores unquoted identifiers in uppercase, so a Snowflake `ORDERS.ORDER_ID` arrives as an uppercase column name. DuckDB is case-insensitive on lookup, so queries keep working, but rename columns during the load if you want lowercase names in MotherDuck.
- **Type mapping.** Snowflake `NUMBER(38,0)` maps to a wide `DECIMAL`, which is slower and larger than a native integer. Cast to `BIGINT` or `INTEGER` during the load when the values fit. `VARIANT`, `OBJECT`, and `ARRAY` come through as JSON strings, so cast them to DuckDB `JSON`, `STRUCT`, or `LIST` types to query them natively.
- **Warehouse required.** Any read from Snowflake, including `INFORMATION_SCHEMA` queries, needs an active warehouse. A missing warehouse shows up as "No active warehouse selected", not as a permissions error.
- **Cost of the read.** The copy runs on Snowflake compute and shows up on your Snowflake bill. Unloading once to Parquet and re-reading the files from MotherDuck is cheaper than repeatedly querying Snowflake during development.

## Related content

- [Ingest Snowflake tables into MotherDuck from a Flight](/cookbook/flight-snowflake-ingest)
- [Apache Iceberg](/integrations/file-formats/apache-iceberg)
- [Loading data from cloud storage or HTTPS](/key-tasks/loading-data-into-motherduck/loading-data-from-cloud-or-https)
- [Data loading patterns](/key-tasks/loading-data-into-motherduck/loading-patterns)
- [Snowflake `COPY INTO <location>` documentation](https://docs.snowflake.com/en/sql-reference/sql/copy-into-location)


---

## 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%2Fsnowflake%2F&page_title=Snowflake&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.
