Skip to content
Docs / Querying and loading data

Documentation

Querying and loading data

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

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 attachmentWorksUse instead
SELECT from wh.my_tableYes
INSERT INTO wh.my_tableYes
CREATE TABLE, CREATE TABLE AS, DROP TABLEYes
Loading a local file into a remote tableYes
Exporting a remote table to a local fileYes
UPDATE and DELETENot yetwh.query('UPDATE …')
SHOW TABLES, information_schemaReturns nothingwh.query('SHOW ALL TABLES')
Tables outside the main schemaNot yetwh.query('SELECT … FROM stripe.invoices')
Joining two remote tablesNot yetPut the whole join inside wh.query('…')
CREATE SCHEMANot yetwh.query('CREATE SCHEMA x')

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”.

DuckDB
-- 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.

DuckDB
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.

DuckDB
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.

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

From Postgres, MySQL or Stripe, continuously

Connections keep tables in sync on a schedule, with no code to run.

Getting data out

DuckDB
-- 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;