HOW-TO

Move Postgres rows into Redshift on a schedule.

By Chris Davidson, founder of yForest · Updated September 26, 2026

Redshift has no native upsert keyword the way Postgres does. Data Sync handles the staged delete-and-insert for you, on a schedule, without a managed connector subscription.

Start 14-day free trial Download on theMac App Store

No credit card. 14 days. Cancel in one click.

macOS 15+ · Apple Silicon native · 14-day free trial · No credit card

Quick answer: Add Postgres and Redshift connections under Databases, then in Pipelines → Build, start a New Sync with Postgres as source and Redshift as target. Map fields, pick Insert, Update, or Upsert, and Dry Run before saving. Pipelines tier; QueryFlow implements Upsert on Redshift as a staged delete-then-insert transaction, since Redshift has no native upsert statement.

Before you start

A working Postgres connection and a working Redshift connection (IAM keys) added under Databases, and a Redshift table already created with the target columns. Pipelines tier. Redshift is on the background helper's supported list; Postgres is not, so the sync needs QueryFlow open, or a Mac left running, to fire unattended.

Steps

  1. Open Pipelines → Build and click New Sync.
  2. On the left, pick your Postgres Connection and Table, or write SQL against it.
  3. On the right, pick your Redshift Connection and target Table.
  4. Drag from a source field to a target field, or click AI Map.
  5. Pick a MODE: Insert, Update, or Upsert.
  6. For Update or Upsert, choose a MATCH ON field.
  7. Click Dry Run, then Save it as a job or run it now.
QueryFlow Data Sync mapper with a Postgres source and Redshift target
Postgres on the left, Redshift on the right, one line per mapped field.

A worked example

An operations team wants a daily copy of a Postgres inventory table landing in Redshift, where the rest of the company's BI tooling already points. The Postgres source:

SELECT
  sku,
  warehouse_id,
  quantity_on_hand,
  updated_at
FROM public.inventory
WHERE updated_at >= now() - interval '1 day';

The Redshift target, schema-qualified the way Redshift (a Postgres-derived engine) expects:

analytics.inventory

Map sku, warehouse_id, quantity_on_hand, and updated_at, set MODE to Upsert, and MATCH ON to a composite of sku and warehouse_id, since inventory is tracked per SKU per location. Redshift doesn't support a single native upsert statement the way Postgres does; QueryFlow handles this by staging the incoming rows and running a delete-then-insert against the matched keys inside a transaction, giving you upsert semantics without you writing that staging logic by hand.

Check it worked

Dry Run first. After a live run, query the Redshift table and spot-check counts against Postgres for the same warehouse and SKU combinations, and watch for any numeric precision differences if a Postgres NUMERIC column maps to a narrower Redshift DECIMAL definition.

Troubleshooting

If you seeFix
No Redshift connections availableAdd one under Databases first, using IAM keys with write access to the target schema.
Job fails on writeConfirm the IAM role or user has INSERT and DELETE privileges on the target table (Redshift's upsert path uses a staged delete-and-insert).
Duplicate key values in source for key column(s) …Pick a MATCH ON column or combination that's unique per row, for example sku plus warehouse_id together rather than sku alone.

Where a managed connector still wins

If Postgres change volume is high and downstream Redshift consumers need near-real-time freshness, a log-based CDC connector avoids both the polling overhead of repeated WHERE-clause queries against a busy production table and the interval-driven lag a scheduled sync has by design. For a daily or hourly inventory rollup, that gap rarely matters in practice, and the tradeoff is a simpler setup with no replication slot to provision or maintain on the Postgres side.

Cost math against Fivetran's own pricing

Per Fivetran's own MAR definition, an inventory table like this one generates MAR proportional to how many distinct SKU/warehouse rows actually change quantity in a given month, not how often the sync runs. Fivetran's pricing page lists a $5 base charge per connection plus per-MAR usage across a 1–1,000,000 MAR band, with a free allowance up to 500,000 MAR. A moderately active inventory table can sit inside or just past that free tier depending on turnover; QueryFlow Pipelines is $29.99/month or $199.99/year flat either way, with the sync running as often as the schedule allows at no extra usage cost.

Type mapping between Postgres and Redshift

Postgres typeRedshift typeNote
INTEGER / BIGINTINTEGER / BIGINTDirect mapping; Redshift's type system is Postgres-derived.
NUMERIC(p,s)DECIMAL(p,s)Match precision and scale exactly to avoid silent rounding on write.
JSONBSUPER or VARCHARRedshift's SUPER type handles semi-structured data closer to Postgres JSONB than a plain VARCHAR would.
TIMESTAMP WITH TIME ZONETIMESTAMPTZRedshift supports this type explicitly; confirm the target column uses it rather than a zone-naive TIMESTAMP.
TEXTVARCHAR(65535)Redshift VARCHAR has a maximum length; very long text values may need truncation logic upstream.

The VARCHAR length ceiling is the detail most likely to surprise someone moving from Postgres, where TEXT columns are effectively unbounded; a Postgres column holding occasional long-form text (a notes field, a JSON blob stored as text) needs its Redshift target sized deliberately, and values exceeding that width will need truncation or a redesign of what gets synced.

Redshift-specific performance considerations

Redshift's columnar storage and distribution key model mean write performance for the sync's target table depends partly on how that table's DISTKEY and SORTKEY are defined, a decision made when the table is created, not something Data Sync controls. For an Upsert-heavy sync running frequently against a large target table, choosing a DISTKEY that aligns with your MATCH ON column (when practical) keeps the staged delete-and-insert pattern QueryFlow uses for Redshift upserts running efficiently rather than triggering a full table scan on every run.

Monitoring the sync over time

Redshift clusters can be paused to save cost when not actively queried; a scheduled sync hitting a paused cluster will fail outright rather than waiting, so if cost-saving pause schedules are in use elsewhere in the organization, coordinate the sync's schedule to run only while the cluster is confirmed to be live, or check the Observatory's run history for a pattern of failures that lines up suspiciously well with a known pause window.

A note on why Redshift lacks native upsert

Redshift's columnar, distributed storage model makes a true in-place row update more expensive than it is on a row-oriented database like Postgres, which is part of why AWS never added a single-statement MERGE or ON CONFLICT equivalent the way Postgres has; the staged delete-and-insert pattern QueryFlow runs under the hood is the standard, AWS-recommended workaround for this exact limitation, not a workaround unique to QueryFlow.

A note on VACUUM and table maintenance

Because Upsert mode on Redshift deletes and re-inserts rows rather than updating them in place, a frequently-run sync against a large target table can accumulate dead rows the way any delete-heavy Redshift workload does, making a periodic VACUUM on the target table worth scheduling separately if the sync runs often enough for that to matter at your table's scale.

A note on connection pooling

A frequently-scheduled sync opening a fresh Postgres connection on every run is rarely a problem at typical sync intervals, but if the same Postgres instance is also serving a busy application with its own connection pool near its configured maximum, adding several very-short-interval scheduled syncs on top can occasionally contend for available connections; checking Postgres's max_connections setting against your total connection count, application plus scheduled jobs, avoids a surprising connection-refused error during a busy period.

One last habit worth building: after any change to either connection's credentials, run Dry Run once before trusting the next scheduled execution, rather than discovering a stale token the next time someone checks the target table.

Sources

Related syncs

See also: Integrations Load a CSV into Redshift. Sync MySQL to Redshift..

Integrations Load a CSV into Redshift. Sync MySQL to Redshift.
QueryFlow Studio $9.99/mo · $99/yr
QueryFlow Pipelines $29.99/mo · $199.99/yr

Frequently asked

Does Redshift really not support native upsert like Postgres?

Correct, Redshift has no single UPSERT or MERGE keyword the way Postgres has ON CONFLICT; the standard pattern is a staged delete-then-insert inside a transaction, which is what QueryFlow's Upsert mode runs against a Redshift target.

Can I sync from a Postgres read replica instead of primary?

Yes, point the Postgres connection at whichever host you use for read traffic; QueryFlow just needs read access to run the source query.

What happens to Postgres NUMERIC precision on the Redshift side?

Make sure the Redshift target column's DECIMAL precision and scale are wide enough to hold the Postgres values without truncation; mismatches here are a common source of small rounding differences.

Is this sync transactional on the Redshift side?

Yes, the staged delete-and-insert for Upsert mode runs inside a transaction, so a failed run doesn't leave the target table in a half-updated state.

Postgres to Redshift, upsert handled for you.

14-day free trial, no card. No staging SQL to write by hand.

Start 14-day free trial

No credit card. 14 days. Cancel in one click.