NetSuite to Snowflake: how to load ERP and GL data your finance team can trust
10 min read Data engineering The Adapters team
Last updated July 2026
To move NetSuite data into Snowflake, extract records through SuiteAnalytics Connect (ODBC/JDBC), SuiteQL over the REST web services, or a managed connector, then land them in raw Snowflake tables and model them into a star schema of transactions and dimensions. Run the extract incrementally using each record's lastModifiedDate as a watermark, and MERGE on internalId so edits and re-runs update rows instead of duplicating them. For finance reporting, carry both the transaction currency amount and the consolidated base amount, and reconcile warehouse totals against the NetSuite trial balance before anyone builds a dashboard on it.
Field mapping auto-plugged · tap a port to rewire
Plug a source port into
Transform on this cable
JSON in
JSON out
5 sample records ready
Key takeaways
- Incremental or bust. Full transaction extracts run into NetSuite's concurrency and governance limits long before they finish.
- Upsert, never append. Prior-period journals get edited and records get deleted, so MERGE on internalId is the only safe load.
- Keep both amounts. Transaction currency, base currency, and the rate used, or your consolidated numbers will drift.
- Tie out to the GL. If the warehouse trial balance doesn't match NetSuite by subsidiary and period, the pipeline isn't done.
How do you connect NetSuite to Snowflake?
You connect them with an extract layer that reads NetSuite records and writes them to Snowflake on a schedule. There are four realistic options: manual saved-search CSV exports, SuiteAnalytics Connect over ODBC or JDBC, SuiteQL and SuiteTalk web services queried programmatically, or a managed integration platform that runs the incremental load for you. Everything else is a variation on those.
The honest comparison matters here, because the cheapest-looking option usually costs the most in analyst hours.
| Method | Freshness | Effort and limits | Best for |
|---|---|---|---|
| Saved search CSV export | Whenever someone remembers | No code, but row caps on exports, manual re-upload, no audit trail | One-off analysis, never production |
| SuiteAnalytics Connect (ODBC/JDBC) | On demand, query-time | Licensed separately from your NetSuite subscription, typically per seat or connection; queries are read-only and can be slow on large transaction tables | Teams with existing ETL tooling that speaks JDBC |
| SuiteQL / REST or SOAP (SuiteTalk) | As often as you schedule it | Real engineering: auth, pagination, governance units, concurrency caps, retry logic, schema drift | Teams with a data engineer to own it |
| Managed connector | Scheduled, minutes to hourly | Watermarking, upserts, retries, and alerting handled; you configure the field map | Finance teams who want the data, not the plumbing |
Whichever route you pick, land raw first and transform in Snowflake. That ELT ordering means a change to how you define net revenue is a SQL change, not a re-extract against a rate-limited ERP. If you're still weighing the two orderings, see ETL vs ELT.
What is SuiteAnalytics Connect?
SuiteAnalytics Connect is NetSuite's data access service that exposes your account through ODBC, JDBC, and ADO.NET drivers, so external tools can query NetSuite records with SQL. It is licensed separately from the core NetSuite subscription and is generally provisioned per user or per connection, which is what makes people think twice before pointing five tools at it.
It's genuinely useful for analysts who want ad hoc SQL against ERP data without waiting on a pipeline. As a production extraction path it has rough edges: query performance on large transaction tables degrades, concurrent connections are constrained, and a long-running analytical query can contend with the same resources your scheduled load needs. Most teams end up using it for exploration and something more purpose-built for the nightly load.
Can you query NetSuite data with SQL?
Yes. SuiteQL is NetSuite's SQL-like query language, available through the REST web services and inside SuiteScript, and it lets you select from record tables with joins and filters rather than pulling whole records one at a time. Combined with SuiteAnalytics Connect for driver-based access, it means you can express extraction as queries instead of record loops.
The catch is governance. NetSuite meters API work in usage units and caps concurrent requests
per account, so a query that scans every transaction since inception will either time out or
starve your other integrations. The fix is the watermark pattern: filter on
lastModifiedDate greater than the high-water mark from your last successful run,
page through the result, and only advance the watermark when the batch has committed in
Snowflake. That turns a multi-million-row table into a few thousand changed rows per run.
Store the watermark with a small safety overlap, a few minutes back from the true maximum, to catch records committed during the previous run's window. The overlap creates duplicates by design, which is fine because the load is an upsert.
How should you model NetSuite data in Snowflake?
Model it as a star schema: one transaction fact table plus dimensions for entities, items, accounts, and subsidiaries. Finance questions are almost always "amount, sliced by something," and that shape answers them cheaply. Keep the raw landing tables close to the NetSuite field names so a new custom field is an added column, not a broken load.
| NetSuite record.field | Snowflake table.column | Type and notes |
|---|---|---|
| Transaction.internalId | FCT_TRANSACTION.transaction_id | NUMBER, primary key, MERGE key |
| Transaction.tranDate | FCT_TRANSACTION.tran_date | DATE, normalize the account timezone before casting |
| Transaction.postingPeriod | FCT_TRANSACTION.period_id | NUMBER, join to DIM_PERIOD, not the same as tran_date |
| Transaction.amount | FCT_TRANSACTION.amount | NUMBER(18,4), transaction currency |
| Transaction.exchangeRate | FCT_TRANSACTION.exchange_rate | NUMBER(18,8), the rate actually applied |
| derived (amount × rate) | FCT_TRANSACTION.base_amount | NUMBER(18,4), consolidated currency, usually USD |
| Transaction.posting | FCT_TRANSACTION.is_posting | BOOLEAN, filter non-posting docs out of GL reporting |
| Entity.internalId | DIM_ENTITY.entity_id | NUMBER, customers and vendors |
| Item.internalId | DIM_ITEM.item_id | NUMBER |
| Account.internalId | DIM_ACCOUNT.account_id | NUMBER, carry acctType for statement grouping |
| Subsidiary.internalId | DIM_SUBSIDIARY.subsidiary_id | NUMBER, carry base currency per subsidiary |
| lastModifiedDate | FCT_TRANSACTION.loaded_at | TIMESTAMP_NTZ, watermark and dedupe ordering key |
Custom fields deserve a plan of their own. NetSuite implementations accumulate
custbody and custcol fields, and they change without warning when an
admin ships a workflow. Land them in a VARIANT column or auto-add typed columns, but don't
hard-code a fixed column list and hope. The same discipline applies to the adjacent spend data
finance wants next to the GL, where tools that
categorize company spend automatically
can fill in the card and vendor detail the ERP records only in summary.
How do you handle multi-currency and multi-subsidiary data in the warehouse?
Store three things per transaction line: the amount in the transaction currency, the amount in the subsidiary's base currency, and the exchange rate NetSuite applied. Never recompute consolidated amounts from a spot rate table after the fact, because NetSuite's rate at posting time is the one the auditors will check. Then key every fact row to a subsidiary so eliminations and per-entity reporting stay possible.
Two more distinctions trip up warehouse builds. First, accounting period is not transaction date: an invoice dated March 31 can post to a different period, and GL reports run by period, so your fact table needs both. Second, posting versus non-posting: sales orders, quotes, and purchase orders live in the same transaction record type as invoices and journals but never hit the GL. Sum them together and your revenue number will be spectacularly wrong.
Closed periods are the reason you can't simply append. Controllers reopen a period, adjust a journal, and re-close it, which changes a row you loaded three weeks ago. Because the extract is keyed on lastModifiedDate, that edited record comes back in the next batch and the MERGE corrects history in place.
How do you handle deletes, edits, and duplicate rows?
Use MERGE on internalId, and dedupe the incoming batch before merging with
ROW_NUMBER() OVER (PARTITION BY internal_id ORDER BY last_modified_date DESC) = 1
so only the newest version of each record lands. That makes the load idempotent: replaying a
failed run updates the same rows rather than double-counting revenue. Deletes need a separate
pass against NetSuite's deleted-records log, flagging rows as is_deleted rather
than hard-deleting them.
Soft deletes matter for finance more than for most domains. If a journal disappears from the warehouse silently, the variance shows up as an unexplained swing in a close package with no audit trail. A deleted flag plus a loaded_at timestamp lets you answer "what changed since the last close, and when."
Then reconcile. Build a query that sums base_amount by account and period for posting transactions only, and compare it to the NetSuite trial balance for the same subsidiary and period. Run it as a scheduled check, not a one-time validation. A tie-out that passed in January will fail in June the first time someone adds a subsidiary you never mapped.
How often should you sync NetSuite to Snowflake?
Hourly for transactions during the month, every fifteen minutes during close if your team works that fast, and nightly for slow dimensions like accounts, subsidiaries, and items. Anything faster rarely changes a decision and does consume governance units that your other NetSuite integrations need. Match cadence to the report that consumes the table.
The bigger risk isn't latency, it's a load that stops quietly. Dashboards keep rendering with stale data, and nobody notices until the controller asks why revenue flatlined on the 4th. Alert on run failure, on zero-row batches during business hours, and on a watermark that hasn't advanced. Pair that with a per-record log so a failed batch is diagnosable instead of mysterious.
The NetSuite to Snowflake integration handles the watermark, the MERGE, retries, alerting, and the log, so the load keeps running through close without someone watching it. If your reporting also depends on CRM data, the Salesforce to Snowflake connector lands pipeline next to the GL in the same warehouse, and the ETL and ELT platform runs both on flat pricing rather than per-row billing that spikes at month end. If you're currently evaluating heavier iPaaS tooling, the Celigo alternative comparison covers where the cost differences show up.
Get NetSuite into Snowflake before the next close
Incremental loads, MERGE on internalId, retries and alerting included. Map the pair in the browser and pay a flat price from $49 a month.