INSERT & UPSERT

INSERT

-- Single row
INSERT INTO users (id, name, email) VALUES ('u1', 'Alice', 'alice@example.com');

-- Multiple rows
INSERT INTO users (id, name) VALUES ('u1', 'Alice'), ('u2', 'Bob');

-- Object literal syntax (schemaless collections)
INSERT INTO users { name: 'Alice', email: 'alice@example.com', age: 30 };

-- INSERT ... SELECT
INSERT INTO archive SELECT * FROM orders WHERE created_at < '2025-01-01';

Plain INSERT is strict: a duplicate primary key raises unique_violation (SQLSTATE 23505). Use ON CONFLICT or UPSERT for "insert if absent" or "insert or overwrite" semantics.

INSERT ... ON CONFLICT

-- Skip rows that would collide with an existing PK (no error)
INSERT INTO users (id, name) VALUES ('u1', 'Alice')
ON CONFLICT DO NOTHING;

-- Overwrite selected fields on conflict. EXCLUDED refers to the incoming row;
-- bare column names refer to the existing row.
INSERT INTO users (id, name, login_count) VALUES ('u1', 'Alice', 1)
ON CONFLICT (id) DO UPDATE SET
  name        = EXCLUDED.name,
  login_count = users.login_count + EXCLUDED.login_count;

ON CONFLICT DO UPDATE fires AFTER UPDATE triggers (not AFTER INSERT) when a row is overwritten — the trigger op tag is derived from storage prior-bytes, not the surface SQL verb.

UPSERT

Insert or overwrite if the primary key already exists:

UPSERT INTO users (id, name, role) VALUES ('u1', 'Alice', 'admin');

-- Object literal syntax
UPSERT INTO users { id: 'u1', name: 'Alice', role: 'admin' };

UPSERT is equivalent to INSERT ... ON CONFLICT (<pk>) DO UPDATE SET <all-columns> = EXCLUDED.<col>. It fires AFTER UPDATE on overwrite and AFTER INSERT on first write.

Bulk Import

-- Auto-detected format (NDJSON, JSON array, or CSV)
COPY users FROM '/path/to/users.ndjson';
COPY users FROM '/path/to/users.csv' WITH (FORMAT csv);

See COPY for the full syntax, all format options, and COPY TO for export.

View page sourceLast updated on May 12, 2026 by Farhan Syah