Visual ETL for PostgreSQL: Clean and Prepare Data Without Complex SQL
Every Monday, the same 45 minutes
Every Monday morning, someone on the sales team exports the week's orders out of PostgreSQL — a few thousand rows, sometimes tens of thousands during a busy quarter. Before that data can go anywhere near a report, they spend the next 45 minutes doing the same three things: filtering out cancelled orders, grouping what's left by region, and re-totaling revenue so leadership sees one clean number per region instead of a wall of transactions.
It's not hard work. It's just the same work, done by hand, every single week — and if that person is out sick on a Monday, the report doesn't go out until they're back.
This is what a visual ETL workflow for PostgreSQL replaces: not the database, not the data, just the repetitive 45 minutes standing between a raw table and a number someone can actually use.
Why this keeps happening
Most teams in this position don't have a data engineer on call and don't have budget for a full BI platform — they have a PostgreSQL database, someone who knows enough SQL to write a query if they have to, and a report that's needed regardless of whether that person has time this week. So the query gets written once, then rewritten from memory (or copy-pasted and edited) every time the report is due, because there's no shared, reusable place for it to live between runs.
The underlying data doesn't change shape from week to week. The process to turn it into a report does — because it's redone from scratch instead of reused. (See SQL vs. visual data prep for a broader look at that tradeoff.)
The manual approach
Concretely, "clean this up by hand" usually looks like:
- Export the relevant table (or run a
SELECT *) into a spreadsheet. - Scroll through and delete or filter out rows where
status = 'cancelled'. - Insert a pivot table or a handful of
SUMIFformulas to total revenue by region. - Sort the result so the biggest region is on top, because that's what gets asked for first.
- Reformat it to look like a report instead of a spreadsheet, and send it.
Every one of those steps is easy in isolation. The problem is that steps 1–5 happen again, from scratch, the next time the report is due — usually by whoever's free that day, not necessarily the person who built the process the week before.
Why the manual version breaks
- It doesn't survive the person who built it. If the SUMIF formulas or the filter logic live in one person's head or one person's spreadsheet, the report has a single point of failure.
- It's error-prone precisely because it's repetitive. Manually filtering "cancelled" out of a status column is exactly the kind of task where a missed row or a typo goes unnoticed until someone downstream asks why the numbers don't match last week's.
- It doesn't scale with the data. A process built for a few thousand rows in a spreadsheet gets noticeably worse — slower, more error-prone, sometimes literally too large to open cleanly — as the export grows.
- Nobody can verify it after the fact. A finished spreadsheet shows a result, not the logic that produced it. Six months later, nobody can easily confirm whether "cancelled" orders were actually excluded correctly.
The same report, built once, on a canvas
Here's the same Monday report, built as a DataQloo workflow against a PostgreSQL orders table — five nodes, no SQL written by hand, and saved so it can be rerun next Monday without rebuilding anything.
Step 1 — connect the source
Drop an Input node on the canvas and select the Sales Orders table via a saved PostgreSQL connection. If a connection isn't saved yet, the Input node's picker configures one inline — host, credentials, and the table to read from.
Step 2 — filter out cancelled orders
Add a Filter node after the Input node and set the condition to status ≠ cancelled. The node's label on the canvas updates immediately to show exactly what's being excluded — no need to open the node again later to remember what it does.
Step 3 — total revenue by region
Add a Summarize node, set the measure to SUM(revenue), and group by region. The node reads "Revenue — SUM · by Region" on the canvas, matching how you'd describe the number out loud to someone else.
Step 4 — sort, biggest region first
Add a Sort node on revenue, descending — because when this report lands in an inbox, the first question is always "which region did the best."
Step 5 — check the result, and the query
Attach a Browse node to see the results grid, the inferred schema, a column profile, and the SQL tab — a read-only view of the exact, parameterized query DataQloo is actually running:
SELECT region, SUM(revenue) AS revenue
FROM sales_orders
WHERE status <> $1
GROUP BY region
ORDER BY revenue DESCNothing here is a paraphrase of what's happening — it's the real query, visible to anyone who wants to check it before trusting the number.
What comes out the other side
A slice of what goes in — a raw pull from sales_orders, cancelled rows included:
| order_id | order_date | customer | region | sales_rep | revenue | status |
|---|---|---|---|---|---|---|
| 1001 | 2026-06-01 | Meridian Retail | West | J. Alvarez | 14,400 | completed |
| 1002 | 2026-06-01 | Bluepeak Inc | North | T. Kim | 3,150 | completed |
| 1003 | 2026-06-02 | Carrow & Sons | South | J. Alvarez | 9,600 | cancelled |
| 1004 | 2026-06-02 | Meridian Retail | West | R. Douglas | 8,000 | completed |
| 1005 | 2026-06-03 | Nova Systems | East | T. Kim | 4,200 | completed |
| 1006 | 2026-06-03 | Carrow & Sons | South | J. Alvarez | 3,600 | completed |
| 1007 | 2026-06-04 | Bluepeak Inc | North | R. Douglas | 3,600 | cancelled |
| 1008 | 2026-06-04 | Nova Systems | East | T. Kim | 6,600 | completed |
And what the workflow produces — cancelled orders excluded, totaled and sorted by region:
| region | revenue |
|---|---|
| West | 22,400 |
| East | 10,800 |
| North | 3,150 |
| South | 3,600 |
That's the entire Monday report. The same five nodes run again next week against whatever new rows have landed in sales_orders — nothing gets rebuilt, and the logic is exactly the same as it was the week before, because it's the same workflow, not a rewritten query.
Key takeaways
- A workflow that filters, summarizes, and sorts PostgreSQL data can be built once on a visual canvas, with zero SQL written by hand.
- The generated query is always visible — not a black box, not a paraphrase of the logic.
- The real advantage isn't the canvas or even PostgreSQL specifically — it's that this workflow gets saved and rerun, instead of rebuilt from scratch the next time the report is due. That's the difference between 45 minutes every Monday and a few seconds.
- The same pattern — connect, filter, summarize, sort — works for any single-table PostgreSQL report: inventory counts, signups by week, expenses by category.
Next
For this same pattern applied to a real recurring deadline, see building a monthly close report without an analyst on call. If your data doesn't live in PostgreSQL at all — a spreadsheet export instead of a live connection — importing a CSV into a reusable dataset walks through the same idea starting from a file instead of a database table.
Try it yourself
Connect a PostgreSQL table and build this exact five-node workflow in the Visual Designer — get early access to try it against your own data.
Related reading
Building a Filtered Revenue Report Without Writing SQL
A walkthrough of building a real workflow — connect, filter, summarize, sort — on the canvas, and what the compiled SQL looks like underneath.
Building a Monthly Close Report Without an Analyst on Call
Close shouldn't wait on whoever knows how to run the report. Here's a profit-by-product close view finance can rerun themselves, every month, without a ticket to the data team.
How to Clean a Messy Spreadsheet Without Writing Formulas
A rep-performance report is only fair if every row actually has a rep attached to it. Here's how to exclude the rows that don't — without a single spreadsheet formula.
Get new posts by email
Engineering notes and product updates from the DataQloo team, occasionally.