Efficient data import and export strategies

Maria Garcia Feb 2026
2 tabs
-- Import CSV with COPY (fastest method)
COPY users (username, email, age, created_at)
FROM '/path/to/users.csv'
WITH (
  FORMAT csv,
  HEADER true,
  DELIMITER ',',
  QUOTE '"',
  NULL 'NULL'
);

-- Export to CSV
COPY users TO '/path/to/users_export.csv'
WITH (
  FORMAT csv,
  HEADER true,
  DELIMITER ',',
  QUOTE '"',
  NULL 'NULL'
);

-- Export query results
COPY (
  SELECT username, email, created_at
  FROM users
  WHERE status = 'active'
  ORDER BY created_at DESC
) TO '/path/to/active_users.csv'
WITH (FORMAT csv, HEADER true);

-- Import tab-delimited file
COPY products (sku, name, price)
FROM '/path/to/products.tsv'
WITH (FORMAT text, DELIMITER E'\t');

-- Import with different NULL representation
COPY sales (date, product_id, quantity, revenue)
FROM '/path/to/sales.csv'
WITH (FORMAT csv, NULL '\N');

-- Binary format (faster, PostgreSQL-specific)
COPY large_table TO '/path/to/data.bin'
WITH (FORMAT binary);

COPY large_table FROM '/path/to/data.bin'
WITH (FORMAT binary);

-- Stream from stdin (pipe from application)
COPY users (username, email) FROM STDIN WITH CSV;
-- Application sends data
-- \. to end

-- Stream to stdout
COPY users TO STDOUT WITH CSV HEADER;

-- psql \copy command (client-side, for non-superuser)
-- \copy users FROM 'users.csv' WITH (FORMAT csv, HEADER true);

-- Import large file efficiently
BEGIN;

-- Disable triggers during import
ALTER TABLE users DISABLE TRIGGER ALL;

-- Drop indexes temporarily
DROP INDEX IF EXISTS idx_users_email;
DROP INDEX IF EXISTS idx_users_username;

-- Import data
COPY users FROM '/path/to/large_users.csv' WITH (FORMAT csv, HEADER true);

-- Recreate indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_username ON users(username);

-- Re-enable triggers
ALTER TABLE users ENABLE TRIGGER ALL;

COMMIT;

-- Analyze after large import
ANALYZE users;

-- Import with data transformation
COPY (
  SELECT
    id,
    UPPER(username) AS username,
    LOWER(email) AS email,
    CURRENT_TIMESTAMP AS imported_at
  FROM staging_users
) TO '/path/to/transformed.csv' WITH CSV;

-- Handle import errors (PostgreSQL 14+)
COPY users FROM '/path/to/users.csv'
WITH (
  FORMAT csv,
  HEADER true,
  ON_ERROR ignore  -- Skip error rows
);

-- Export JSON
COPY (
  SELECT jsonb_build_object(
    'id', id,
    'username', username,
    'email', email,
    'created_at', created_at
  )
  FROM users
) TO '/path/to/users.json';

-- Export JSONL (JSON Lines)
COPY (
  SELECT row_to_json(users.*)
  FROM users
) TO '/path/to/users.jsonl';

-- Incremental export (only new/updated records)
COPY (
  SELECT *
  FROM users
  WHERE updated_at > (
    SELECT last_export_time
    FROM export_log
    WHERE table_name = 'users'
  )
) TO '/path/to/users_incremental.csv' WITH CSV HEADER;

-- Update last export time
UPDATE export_log
SET last_export_time = CURRENT_TIMESTAMP
WHERE table_name = 'users';
2 files · sql Explain with highlit

Data import/export moves data between systems. I use COPY for bulk operations—orders of magnitude faster than INSERT. CSV format balances simplicity and performance. Binary format is faster but less portable. Streaming import handles large files without memory issues. Parallel import utilizes multiple cores. ETL pipelines transform data during import. Understanding delimiters, quoting, escaping prevents data corruption. Header rows simplify column mapping. NULL handling requires explicit configuration. Foreign key constraints can slow imports—disable temporarily. Batching balances transaction size and performance. pg_dump exports databases reliably. JSON export integrates with modern APIs. Proper import/export strategy is essential for data migration, backups, analytics pipelines.