Extract and Load¶
BigQuery provides native batch operations for moving data in both directions:
-
Load: A BigQuery load job imports batch data from local files or Cloud Storage into a BigQuery table. The job handles formats such as CSV, JSON, Avro, Parquet, and ORC, including schema detection and whether data is appended or overwritten.
-
Export: BigQuery exports table data to Cloud Storage with an extract job—the same operation exposed by the
bq extractcommand. Google documents this under Export table data to Cloud Storage: export is the user-facing capability, while extract job is the underlying BigQuery job type.
Use bigquery_load and bigquery_extract to run these operations from DuckDB
SQL. Both functions wait for completion and return the job identity, status,
and transfer statistics; the following sections cover their inputs, options,
and operational requirements.
Load Data into BigQuery¶
bigquery_load creates a BigQuery load job with exactly one source family. A
local file is uploaded directly, Cloud Storage URIs are read by BigQuery, and
a DuckDB table or view is first written to a temporary Parquet file. All three
paths write to a BigQuery dataset.table.
Load a local file:
-- Load a local Parquet file into a BigQuery table.
SELECT *
FROM bigquery_load(
'my-gcp-project',
'my_dataset.events',
source_file := '/absolute/path/events.parquet',
source_format := 'PARQUET',
billing_project := 'my-billing-project'
);
┌─────────┬──────────────┬────────────────┬──────────┬──────────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ location │ destination_table │ output_rows │ status │
│ boolean │ varchar │ varchar │ varchar │ varchar │ uint64 │ json │
├─────────┼──────────────┼────────────────┼──────────┼──────────────────────────────────┼─────────────┼──────────────────┤
│ true │ load_job_123 │ my-gcp-project │ EU │ my-gcp-project.my_dataset.events │ 1250 │ {"state":"DONE"} │
└─────────┴──────────────┴────────────────┴──────────┴──────────────────────────────────┴─────────────┴──────────────────┘
-- Attach the destination dataset with its billing project.
ATTACH 'project=my-gcp-project dataset=my_dataset billing_project=my-billing-project'
AS bq (TYPE bigquery);
-- Load a local Parquet file through the attached catalog.
SELECT *
FROM bigquery_load(
'bq',
'my_dataset.events',
source_file := '/absolute/path/events.parquet',
source_format := 'PARQUET'
);
┌─────────┬──────────────┬────────────────┬──────────┬──────────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ location │ destination_table │ output_rows │ status │
│ boolean │ varchar │ varchar │ varchar │ varchar │ uint64 │ json │
├─────────┼──────────────┼────────────────┼──────────┼──────────────────────────────────┼─────────────┼──────────────────┤
│ true │ load_job_234 │ my-gcp-project │ EU │ my-gcp-project.my_dataset.events │ 1250 │ {"state":"DONE"} │
└─────────┴──────────────┴────────────────┴──────────┴──────────────────────────────────┴─────────────┴──────────────────┘
The job ID and row count in the example output depend on the submitted file.
Load one or more Cloud Storage objects. URI wildcards are passed to BigQuery:
-- Append matching Cloud Storage objects to a BigQuery table.
SELECT *
FROM bigquery_load(
'my-gcp-project',
'my_dataset.events',
source_uris := ['gs://my-bucket/events/part-*.parquet'],
source_format := 'PARQUET',
write_disposition := 'WRITE_APPEND'
);
┌─────────┬──────────────┬────────────────┬──────────┬──────────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ location │ destination_table │ output_rows │ status │
│ boolean │ varchar │ varchar │ varchar │ varchar │ uint64 │ json │
├─────────┼──────────────┼────────────────┼──────────┼──────────────────────────────────┼─────────────┼──────────────────┤
│ true │ load_job_345 │ my-gcp-project │ EU │ my-gcp-project.my_dataset.events │ 5000 │ {"state":"DONE"} │
└─────────┴──────────────┴────────────────┴──────────┴──────────────────────────────────┴─────────────┴──────────────────┘
-- Attach the destination dataset.
ATTACH 'project=my-gcp-project dataset=my_dataset'
AS bq (TYPE bigquery);
-- Append matching Cloud Storage objects through the attached catalog.
SELECT *
FROM bigquery_load(
'bq',
'my_dataset.events',
source_uris := ['gs://my-bucket/events/part-*.parquet'],
source_format := 'PARQUET',
write_disposition := 'WRITE_APPEND'
);
┌─────────┬──────────────┬────────────────┬──────────┬──────────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ location │ destination_table │ output_rows │ status │
│ boolean │ varchar │ varchar │ varchar │ varchar │ uint64 │ json │
├─────────┼──────────────┼────────────────┼──────────┼──────────────────────────────────┼─────────────┼──────────────────┤
│ true │ load_job_456 │ my-gcp-project │ EU │ my-gcp-project.my_dataset.events │ 5000 │ {"state":"DONE"} │
└─────────┴──────────────┴────────────────┴──────────┴──────────────────────────────────┴─────────────┴──────────────────┘
BigQuery reads Cloud Storage sources itself, so the job identity needs access to the objects. The bucket must also satisfy BigQuery's location rules for the destination dataset. Google's batch loading documentation describes supported Cloud Storage layouts, wildcard behavior, permissions, locations, service limits, and format-specific restrictions.
To load a DuckDB relation, pass its catalog-qualified name through
source_table:
-- Prepare a local DuckDB relation for the load job.
CREATE TEMP TABLE prepared_events AS
SELECT *
FROM read_parquet('/absolute/path/source/*.parquet');
-- Replace the destination table with the prepared relation.
SELECT *
FROM bigquery_load(
'my-gcp-project',
'my_dataset.events',
source_table := 'prepared_events',
write_disposition := 'WRITE_TRUNCATE'
);
┌─────────┬──────────────┬────────────────┬──────────┬──────────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ location │ destination_table │ output_rows │ status │
│ boolean │ varchar │ varchar │ varchar │ varchar │ uint64 │ json │
├─────────┼──────────────┼────────────────┼──────────┼──────────────────────────────────┼─────────────┼──────────────────┤
│ true │ load_job_567 │ my-gcp-project │ EU │ my-gcp-project.my_dataset.events │ 1250 │ {"state":"DONE"} │
└─────────┴──────────────┴────────────────┴──────────┴──────────────────────────────────┴─────────────┴──────────────────┘
-- Attach the destination dataset.
ATTACH 'project=my-gcp-project dataset=my_dataset'
AS bq (TYPE bigquery);
-- Prepare a local DuckDB relation for the load job.
CREATE TEMP TABLE prepared_events AS
SELECT *
FROM read_parquet('/absolute/path/source/*.parquet');
-- Replace the destination table through the attached catalog.
SELECT *
FROM bigquery_load(
'bq',
'my_dataset.events',
source_table := 'prepared_events',
write_disposition := 'WRITE_TRUNCATE'
);
┌─────────┬──────────────┬────────────────┬──────────┬──────────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ location │ destination_table │ output_rows │ status │
│ boolean │ varchar │ varchar │ varchar │ varchar │ uint64 │ json │
├─────────┼──────────────┼────────────────┼──────────┼──────────────────────────────────┼─────────────┼──────────────────┤
│ true │ load_job_678 │ my-gcp-project │ EU │ my-gcp-project.my_dataset.events │ 1250 │ {"state":"DONE"} │
└─────────┴──────────────┴────────────────┴──────────┴──────────────────────────────────┴─────────────┴──────────────────┘
The extension stages the relation as a temporary local Parquet file, uploads it, and removes it after the operation. The temporary directory must have enough free space for the complete staged relation.
The extension accepts Parquet, CSV, newline-delimited JSON, Avro, and ORC.
Recognized and consistent file suffixes can determine the format; otherwise
specify source_format. CSV, JSON, Avro, Parquet, ORC, schema evolution, and
Hive partitioning each have additional options in the
bigquery_load reference. Google's
loading overview
places batch load jobs alongside streaming, transfer, and federation options
that are outside this function's scope.
Default write disposition
bigquery_load defaults to WRITE_TRUNCATE. Set
write_disposition := 'WRITE_APPEND' to append, or WRITE_EMPTY to
require an empty destination. create_disposition independently controls
whether BigQuery may create a missing table.
Extract a Table to Cloud Storage¶
bigquery_extract creates an extract job for one BigQuery table and writes one
or more objects to Cloud Storage:
-- Export a BigQuery table to compressed Parquet objects.
SELECT *
FROM bigquery_extract(
'my-gcp-project',
source_table := 'my_dataset.events',
destination_uris := 'gs://my-bucket/exports/events-*.parquet',
format := 'PARQUET',
compression := 'ZSTD',
billing_project := 'my-billing-project'
);
┌─────────┬─────────────────┬────────────────┬───┬─────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ … │ destination_uri_file_counts │ input_bytes │ status │
│ boolean │ varchar │ varchar │ … │ bigint[] │ bigint │ json │
├─────────┼─────────────────┼────────────────┼───┼─────────────────────────────┼─────────────┼──────────────────┤
│ true │ extract_job_123 │ my-gcp-project │ … │ [2] │ 983040 │ {"state":"DONE"} │
└─────────┴─────────────────┴────────────────┴───┴─────────────────────────────┴─────────────┴──────────────────┘
-- Attach the source dataset with its billing project.
ATTACH 'project=my-gcp-project dataset=my_dataset billing_project=my-billing-project'
AS bq (TYPE bigquery);
-- Export a table through the attached catalog.
SELECT *
FROM bigquery_extract(
'bq',
source_table := 'my_dataset.events',
destination_uris := 'gs://my-bucket/exports/events-*.parquet',
format := 'PARQUET',
compression := 'ZSTD'
);
┌─────────┬─────────────────┬────────────────┬───┬─────────────────────────────┬─────────────┬──────────────────┐
│ success │ job_id │ project_id │ … │ destination_uri_file_counts │ input_bytes │ status │
│ boolean │ varchar │ varchar │ … │ bigint[] │ bigint │ json │
├─────────┼─────────────────┼────────────────┼───┼─────────────────────────────┼─────────────┼──────────────────┤
│ true │ extract_job_234 │ my-gcp-project │ … │ [2] │ 983040 │ {"state":"DONE"} │
└─────────┴─────────────────┴────────────────┴───┴─────────────────────────────┴─────────────┴──────────────────┘
Job IDs, file counts, and byte counts vary by execution. DuckDB may collapse
wide result sets with …; the
bigquery_extract reference
lists every result column.
The source is a table identifier, not arbitrary GoogleSQL. To export the
result of a query, either materialize it into a table first or execute
GoogleSQL's
EXPORT DATA statement
with bigquery_execute.
The destination must use gs://. Use a wildcard when BigQuery may need to
produce multiple objects. BigQuery limits a single export file to 1 GB of
logical data; larger exports require multiple files. Output order is not
guaranteed for a table extract. Google's
table export documentation
explains destination URI rules, location requirements, export limits, and
Cloud Storage behavior.
The extension supports CSV, newline-delimited JSON, Avro, and Parquet exports.
It can infer a format when every destination URI has a consistent recognized
suffix, or format can be set explicitly. Compression is format-specific:
| Format | Supported compression |
|---|---|
| CSV | NONE, GZIP |
| Newline-delimited JSON | NONE, GZIP |
| Avro | NONE, DEFLATE, SNAPPY |
| Parquet | NONE, GZIP, SNAPPY, ZSTD |
CSV cannot represent nested and repeated BigQuery values. Prefer Avro,
newline-delimited JSON, or Parquet for those schemas. The
bigquery_extract reference
contains the complete parameters, format inference rules, CSV and Avro
options, and result columns.
Permissions, Locations, and Job Completion¶
Load and extract calls require permission to create BigQuery jobs, access to the source, permission to modify the load destination, and the relevant Cloud Storage access. The exact IAM set depends on the direction and source type; start with the project's required permissions and the operation-specific permissions in Google's load and export documentation.
The BigQuery job location and Cloud Storage bucket location must be compatible
with the dataset involved in the transfer. Pass location when it cannot be
derived or when bq_default_location is not appropriate. A mismatched location
can make an otherwise valid transfer fail.
Both functions wait for BigQuery and return the remote job ID, project,
location, status, and operation-specific statistics. A local timeout_ms
stops waiting in DuckDB but does not guarantee cancellation: the remote job can
continue reading, writing, or incurring charges. Use the returned job_id
with Executing & Monitoring Jobs to
inspect a timed-out or completed transfer.
Google's job management guide describes the BigQuery job lifecycle. Load and extract operations are also subject to BigQuery's current quotas and limits. Review Billing and Costs before submitting large transfers.