# Querying and loading data

> What works through an attached database, how to list tables, and how to move data in and out.

Source: https://duckhouse.co/docs/querying

An attached DuckHouse database works like a local one for everyday reads and writes. Quack, the protocol underneath, is new, and a handful of things do not work through the attachment yet. Each has a one-line alternative, and this page covers all of them.

## The short version

| Through the attachment | Works | Use instead |
| --- | --- | --- |
| SELECT from `wh.my_table` | Yes |  |
| INSERT INTO `wh.my_table` | Yes |  |
| CREATE TABLE, CREATE TABLE AS, DROP TABLE | Yes |  |
| Loading a local file into a remote table | Yes |  |
| Exporting a remote table to a local file | Yes |  |
| UPDATE and DELETE | Not yet | `wh.query('UPDATE …')` |
| SHOW TABLES, information\_schema | Returns nothing | `wh.query('SHOW ALL TABLES')` |
| Tables outside the `main` schema | Not yet | `wh.query('SELECT … FROM stripe.invoices')` |
| Joining two remote tables | Not yet | Put the whole join inside `wh.query('…')` |
| CREATE SCHEMA | Not yet | `wh.query('CREATE SCHEMA x')` |

> Using DuckPlus?
>
> [DuckPlus](https://duckhouse.co/duckplus), our desktop DuckDB IDE, runs every statement on the server, so nothing in this table applies there. Write SQL as you would locally. See [Connecting](https://duckhouse.co/docs/connect.md#duckplus).

## wh.query() runs SQL on the server

Every attached database has a `query()` function. The SQL you give it runs inside your DuckHouse database rather than on your machine, and only the result comes back. Nothing on the list above applies there, so it is the answer to every row marked “not yet”.

```sql
-- List everything
FROM wh.query('SHOW ALL TABLES');

-- Describe one table
FROM wh.query('DESCRIBE events');

-- Change and remove rows
FROM wh.query('UPDATE events SET status = ''done'' WHERE id = 42');
FROM wh.query('DELETE FROM events WHERE created_at < now() - INTERVAL 90 DAY');
```

Inside the string, write a single quote as two (`''done''`), and refer to tables by the names they have on the server: `events`, not `wh.events`.

### It is also the fast way to join

A join written inside `query()` runs next to the data, and only the joined result crosses the network. For large tables that is the difference between moving a few rows and moving both tables.

```sql
FROM wh.query('
  SELECT c.email, sum(i.amount_paid) / 100 AS paid
  FROM stripe.customers c
  JOIN stripe.invoices i ON i.customer = c.id
  WHERE i.status = ''paid''
  GROUP BY c.email
  ORDER BY paid DESC
  LIMIT 20
');
```

Joining a remote table to a *local* one works normally, because only one side is remote: `SELECT … FROM wh.events e JOIN my_local_table l USING (id)`.

## Loading data

### From a file on your machine

DuckDB reads the file locally and streams it up. Parquet, CSV and JSON all work.

```sql
CREATE TABLE wh.orders AS SELECT * FROM 'orders.parquet';

-- Add more later
INSERT INTO wh.orders SELECT * FROM 'orders_2026_09.csv';
```

### From a URL or a bucket

If the data is already online, have the server fetch it. Nothing passes through your machine, so this is the quickest way to load anything large.

```sql
FROM wh.query('
  CREATE TABLE trips AS
  SELECT * FROM read_parquet(''https://example.com/data/trips.parquet'')
');
```

### From Postgres, MySQL or Stripe, continuously

[Connections](https://duckhouse.co/docs/connections.md) keep tables in sync on a schedule, with no code to run.

## Getting data out

```sql
-- To a local file
COPY (SELECT * FROM wh.orders WHERE year = 2026) TO 'orders_2026.parquet';

-- To a local table, for offline work
CREATE TABLE orders_local AS SELECT * FROM wh.orders;
```

> Will these gaps close?
>
> Probably. They are limits of DuckDB's Quack client, not of DuckHouse, and DuckDB 2.0 lists better compatibility for Quack among its changes. We re-test every item on this page with each DuckDB release and update it. See [Versions and limitations](https://duckhouse.co/docs/limits.md).
