NetSuite SuiteQL over REST for budget-vs-actuals: what can be established without an account, and the test plan for when we get one
The question
Confirm against a live NetSuite account whether SuiteQL over REST can return budget records joined to actuals, and what the practical row/timeout ceiling is.
The live-account test remains outstanding and was NOT run for this brief. RDCO has no NetSuite account, sandbox, or partner instance as of 2026-07-26. Nothing below was executed against a real NetSuite instance. What this brief delivers instead is (1) everything establishable from vendor documentation and independent practitioner evidence, (2) a copy-pasteable test plan with pass/fail criteria and outcome routing, and (3) a provisional answer with an explicit confidence label. This question is BLOCKING for the ab-managerial-accounting brigade: the answer selects between a native API pull, a transaction-line reconstruction, and a paid licensed-ODBC dependency.
Evidence in this brief is tagged: [DOC] = vendor primary documentation, [PRACT] = independent practitioner report, [INFER] = Ray's inference, [UNKNOWN] = could not be sourced, deliberately not guessed.
What we already know (from the vault)
- This question was raised as a follow-up by its own parent brief. [[2026-07-19-midmarket-variance-analysis-data-inputs]] established the contradiction that generates it: Oracle's canonical "Extracting Data from NetSuite" page lists exactly five extraction paths (Full CSV Export, Saved Search export, Report export, SuiteTalk SOAP, SuiteAnalytics Connect via ODBC/JDBC/ADO.NET) and does not mention SuiteQL or REST at all, while the practitioner web treats SuiteQL-over-REST as the default modern answer. That brief's own verdict was "treat SuiteQL as available but verify against a live account before it becomes a scoping assumption." This brief is that verification attempt, minus the account.
- NetSuite is the middle automatability tier, and this answer is what fixes its position. Same brief: Intacct is automatable end to end via a single
get_accountbalancesbydimensionscall withcontentselection = Actual and Budget; QuickBooks Desktop Enterprise is human-export-step for v1. NetSuite was scoped as "automatable with real work" precisely because the budget side was unresolved. - SuiteAnalytics Connect is a commercial gate, not just a technical one. Oracle's own extraction page states ODBC access is an additional service with an associated fee ([[2026-07-19-midmarket-variance-analysis-data-inputs]], sourced to docs.oracle.com). If the test fails, the fallback is not "more engineering," it is "the client buys a license."
- The canonical intermediate schema already exists as a design decision. The parent brief specifies
period, entity, account_id, account_name, account_path, statement_section, department, class, location, project, scenario, amount, currencywithscenarioin {actual, budget, forecast, prior}. That schema is what any NetSuite adapter must fill. The test plan below is written to produce exactly those columns, so a passing test is also a working adapter prototype. - Sign convention is the named one-line-bug risk. Same brief: NetSuite stores credits negative on transaction lines while reporting revenue positive. Any budget-vs-actual join must reconcile budget sign against actuals sign, and this is a test-plan assertion, not a footnote.
What the web says
- [DOC] The REST SuiteQL endpoint is real, documented, and Oracle publishes its contract.
POST https://<account>.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql, with the query in the request body under the parameterq. Oracle states plainly: "Note thatPrefer: transientis a required header parameter." Pagination is vialimitandoffsetURL parameters (docs.oracle.com, Executing SuiteQL Queries Through REST Web Services). This alone resolves the parent brief's "docs page is likely stale rather than wrong" hedge: SuiteQL over REST is first-party documented, just not on the extraction-overview page. - [DOC] The hard row ceiling is 100,000 results without SuiteAnalytics Connect. Verbatim from the same page: "Using SuiteQL queries, you can return a maximum of 100,000 results. If you expect your queries to return more than 100,000 results, you can use SuiteAnalytics Connect with the NetSuite2.com data source."
- [DOC] The paging doc gives the shape of that ceiling. Oracle's Collection Paging page: "The results are displayed on multiple pages, with the default setting of 1000 results per page on a maximum of 1000 pages," and "This way you can return maximum one million results if you use SuiteAnalytics Connect. Without SuiteAnalytics Connect, you can display maximum 100 000 results." It also imposes a constraint people trip over: "You can only set offset and limit values in a way that the offset is divisible by the limit. For example, Offset=20, Limit=10 or Offset=0, Limit=5" (docs.oracle.com, Collection Paging).
- [UNKNOWN] Oracle publishes no timeout number on either page. Neither the SuiteQL REST page nor the Collection Paging page states a request-duration limit, a per-query timeout, or a concurrency governance figure. Practitioner chatter references timeouts on wide/heavy queries but I could not attach a number to a named source. The timeout ceiling is unknown and is the single most important thing the live test must measure. Do not put a timeout figure in any client-facing scoping doc until the test produces one.
- [PRACT] The budget tables are named, column-level documented, and reported queryable via SuiteQL. Tim Dietrich's "Use SuiteQL to Access Budget Information" (2023-07-12) identifies two tables:
Budgets(annual budgeted amounts by GL account, subsidiary, department, category) andBudgetsMachine(period-level detail, also surfaced in the UI as "Budget Line" or "Budget by Period"), joined againstAccountingPeriodandAccount. Columns named:ID,PeriodName,IsYear,Year,Subsidiary,Department,Category,Account,Total,Period,Amount,AcctType. Critical caveat the author states: querying these tables requires the "Transactions - Set Up Budgets" permission (timdietrich.me). - [PRACT] The same source explicitly did NOT demonstrate the join to actuals. Dietrich closes with "In a future post, I'll show how you can extend these queries to get the 'actual' values." No follow-up post surfaced in search. This is the exact gap the question asks about, and the best available practitioner source stops one step short of it. Treat any claim that "SuiteQL budget-vs-actuals is a solved pattern" as unsupported by primary practitioner evidence.
- [PRACT] The actuals side is well-trodden independently.
TransactionAccountingLinecarries the accounts a transaction impacted plus debit and credit amounts, and Dietrich has separate posts on GL impact by transaction type and on journal-entry GL impact (timdietrich.me). So both halves of the join are individually established; only the join itself is unverified. - [PRACT, low weight] Aggregator and vendor-marketing content asserts the join works. Several Coefficient use-case pages claim custom SuiteQL queries join project budgets to actual transaction data for variance analysis, showing fragments like
SELECT p.entityid, a.name, SUM(tl.amount) FROM transaction t JOIN job p ON tl.job = p.id. These are spreadsheet-connector marketing pages with an interest in the answer being "yes, but it's painful," and the SQL fragments shown are not complete or self-consistent. Do not cite these as evidence. Logged only to note that no independent source contradicts feasibility. - [DOC, surfaced not fetched] Oracle itself ships SuiteQL against budget-adjacent data. A search result surfaced an Oracle page titled "Managing SuiteQL Queries in Planning and Budgeting Sync" (docs.oracle.com). Not fetched, so I claim nothing about its contents beyond the title. The title alone is a meaningful signal that Oracle's own NetSuite Planning & Budgeting integration uses SuiteQL as its transport, which would make budget-table access a supported first-party pattern rather than a community hack. Verify this page before it becomes load-bearing.
- Paywall/access flag: the NetSuite Professionals community archive thread on SuiteQL REST pagination caps (
archive.netsuiteprofessionals.com/t/10469738/) returned HTTP 403 Forbidden. Not retried per research protocol. That thread reportedly discusses a page cap and offset cap; its contents are unknown to this brief. Anyone with community access should read it before the test, it may pre-answer the ceiling question.
Convergences and contradictions
- Convergence, and it resolves the parent brief's open contradiction. [[2026-07-19-midmarket-variance-analysis-data-inputs]] flagged that Oracle's extraction-overview page omits SuiteQL while practitioners rely on it, and guessed "stale rather than wrong." That guess is now confirmed: Oracle documents the SuiteQL REST endpoint in detail elsewhere in the same help center. The extraction-overview page is simply not maintained as the index it appears to be. Operational lesson beyond NetSuite: an ERP vendor's "how to extract data" overview page is a marketing-adjacent artifact, not a capability inventory. Always cross-check against the API reference.
- Contradiction between the vendor ceiling and the practitioner ceiling framing. Oracle's paging doc says "default setting of 1000 results per page on a maximum of 1000 pages," which arithmetically implies 1,000,000, then immediately says 100,000 without SuiteAnalytics Connect. Both numbers are on the same page. [INFER] The reconcilable reading is that the 1000-pages figure is the generic REST collection cap and the 100,000 figure is a SuiteQL-specific overlay, with the lower number binding. Community reports of an offset that cannot be pushed past 100,000 are consistent with that reading. This is a test-plan assertion, not a settled fact.
- The load-bearing gap is narrower than it looks. Budget-side readability: one named practitioner source with column-level detail, plus a probable first-party Oracle usage. Actuals-side readability: multiple independent sources. The join: zero direct evidence either way. So the risk is not "can SuiteQL see budgets" (likely yes), it is "does the join survive grain mismatch, dialect limits, and the row ceiling in one statement" (genuinely open).
Synthesis for RDCO
Provisional answer, explicitly NOT live-verified. SuiteQL over REST can very probably return budget records, and can very probably return actuals, and the two can very probably be joined - but the honest confidence split is uneven and should be carried that way into scoping. Budget-table readability via SuiteQL: MEDIUM-HIGH confidence (one named practitioner source at column level, one probable first-party Oracle usage, no contradicting source). Actuals readability via TransactionAccountingLine: HIGH confidence (multiple independent sources, widely used pattern). A single-statement budget-joined-to-actuals result set: LOW-MEDIUM confidence (no source demonstrates it; the best source explicitly deferred it to a post that never appeared). The 100,000-row ceiling without SuiteAnalytics Connect: HIGH confidence, Oracle primary, quoted verbatim. The timeout ceiling: UNKNOWN, no number from any named source, and I am deliberately not producing one.
The ceiling is probably not binding for the actual use case, and that matters more than the ceiling itself. [INFER] The brigade does not need to stream raw transaction lines; it needs a pre-aggregated grid at account × period × dimension × scenario. A mid-market client at 200-800 GL accounts, 12 periods, and say 20 departments is 48,000 to 192,000 dense cells - so the dense cross-product straddles the 100,000 line, but real grids are sparse because most accounts never post to most departments. The practical read is that a server-side-aggregated pull (GROUP BY in the SuiteQL statement, one row per populated cell) lands comfortably under 100,000 for a single fiscal year at a single client, while a client-side-aggregated pull (fetch raw lines, sum locally) blows through it on any real transaction volume. That single architectural choice - aggregate in SuiteQL, not in Python - is what decides whether NetSuite is API-ingestible or ODBC-gated, and it is decidable before the test. Design the adapter that way regardless of outcome.
Route the three ingestion paths off the test, not off a guess. Path A, native API pull: selected if the budget tables are readable with a normal integration role and a server-side-aggregated budget-joined-to-actuals statement returns correct numbers under 100,000 rows within an acceptable wall clock. Path B, transaction-line reconstruction: selected if actuals are readable but budgets are not (permission-blocked, table absent, or the client's budget simply does not live in NetSuite - which the parent brief flags as the common mid-market case anyway). Under Path B the budget adapter becomes a separate integration against Excel or a planning tool, and the NetSuite adapter only ever delivers the actuals half. Path C, paid SuiteAnalytics Connect: selected only if the aggregated pull still exceeds 100,000 rows or times out. [INFER] Path B is the most likely real-world outcome for reasons that have nothing to do with SuiteQL - the parent brief already established that for most mid-market teams the budget does not live in the ERP. That reframes the test's value: it is less "can we do this" and more "for the subset of clients who do budget inside NetSuite, is the happy path available."
The commercial framing should change now, before the test. Under any outcome, NetSuite ingestion is a two-adapter build (actuals adapter plus a budget adapter that may or may not point at NetSuite), while Intacct is one call. That difference is real, it is knowable today, and it should be priced today. Do not quote a NetSuite client the Intacct SLA, and do not wait for a live test to make that distinction. What the test changes is narrower: whether a NetSuite engagement carries a client-side license dependency (Path C) or not. That is a contract-risk question, and it is worth surfacing to any prospect as a discovery question - "does your budget live in NetSuite, and do you have SuiteAnalytics Connect?" - which incidentally is a faster way to get the answer than acquiring a test account.
Test plan (run when a live account is available)
Prerequisites
- NetSuite account with the REST Web Services feature enabled (
Setup > Company > Enable Features > SuiteCloud). - An integration record and access token. TBA (OAuth 1.0a) or OAuth 2.0 machine-to-machine. Do not use a UI login.
- A role carrying, at minimum: REST Web Services permission, SuiteAnalytics Workbook permission [PRACT, verify], and Transactions - Set Up Budgets [PRACT, Dietrich - this is the one most likely to be missing on a stock integration role, and its absence is itself a finding].
- Every request:
POST /services/rest/query/v1/suiteql, headerPrefer: transient[DOC, required],Content-Type: application/json, body{"q": "<statement>"}.
Sourcing a live account [INFER]: cheapest legitimate paths, in order - (a) an existing client or prospect grants a read-only integration role in their sandbox, which doubles as a discovery conversation; (b) an Oracle NetSuite SuiteCloud Developer Network / partner NFR development account; (c) a contact whose employer runs NetSuite. Note that (a) also answers the more important commercial question (does the budget live in NetSuite) at the same time.
Step 0 - Schema discovery (do this first; it de-risks every statement below)
Column and foreign-key names below are reconstructed from a 2023 practitioner post and are not verified against the current Records Catalog. Resolve them empirically before assuming any statement is correct.
SELECT * FROM budgets WHERE ROWNUM <= 1
SELECT * FROM budgetsmachine WHERE ROWNUM <= 1
SELECT * FROM transactionaccountingline WHERE ROWNUM <= 1
Also open the Records Catalog in the account UI (Analytics > Datasets > New, or the SuiteAnalytics record browser) and confirm whether budget, budgetimport, and budgetexchangerate appear as SuiteQL-addressable tables. [UNKNOWN] No source consulted for this brief confirms budgetimport or budgetexchangerate exposure to SuiteQL. Record what you find.
Dialect note to resolve here: SuiteQL is Oracle-flavored. Confirm whether ROWNUM, FETCH FIRST n ROWS ONLY, WITH (CTEs), and FULL OUTER JOIN are each accepted. Practitioner reports on CTE support are inconsistent and I found no primary source. If CTEs are unavailable, the Step 4 statement must be rewritten with inline subqueries.
Step 1 - Budget side readable?
SELECT
b.id, b.year, b.subsidiary, b.department, b.category, b.account, b.total
FROM budgets b
WHERE ROWNUM <= 10
PASS: rows return with recognizable budget amounts. FAIL modes to distinguish and record separately: (a) HTTP 403 / permission error -> role lacks "Transactions - Set Up Budgets", fixable, retry with an amended role; (b) "invalid record type" / unknown table -> the table is not SuiteQL-addressable in this NetSuite version, a real capability finding; (c) zero rows -> the account has no budgets loaded, test is inconclusive, find another account.
Step 2 - Period-level budget grain
SELECT
bm.period, bm.account, bm.amount
FROM budgetsmachine bm
WHERE ROWNUM <= 10
Then resolve the FK from budgetsmachine back to budgets using the Step 0 output and run the joined form:
SELECT
ap.periodname, b.subsidiary, b.department, b.category,
a.acctnumber, a.acctname, bm.amount
FROM budgets b
INNER JOIN budgetsmachine bm ON bm.<budget_fk> = b.id
INNER JOIN accountingperiod ap ON ap.id = bm.period
INNER JOIN account a ON a.id = b.account
WHERE ap.startdate >= TO_DATE('2026-01-01','YYYY-MM-DD')
PASS: monthly budget amounts by account and department. This is the make-or-break step for Path A - annual-only budget (Step 1 only, no period grain) is not sufficient for monthly variance commentary.
Step 3 - Actuals side, server-side aggregated
SELECT
t.postingperiod AS period_id,
tal.account AS account_id,
tl.department AS department_id,
tl.class AS class_id,
tl.subsidiary AS subsidiary_id,
SUM(tal.amount) AS actual_amount
FROM transactionaccountingline tal
INNER JOIN transactionline tl
ON tl.transaction = tal.transaction
AND tl.id = tal.transactionline
INNER JOIN transaction t
ON t.id = tal.transaction
WHERE t.posting = 'T'
AND tal.posting = 'T'
GROUP BY t.postingperiod, tal.account, tl.department, tl.class, tl.subsidiary
PASS: aggregated actuals, one row per populated cell. Assert before proceeding: tie the sum for one revenue account and one expense account for one period against the same figures in the NetSuite UI Income Statement. This is the sign-convention check the parent brief flags as the single most embarrassing available failure. Record whether revenue comes back negative.
Step 4 - The actual question: budget joined to actuals in one statement
SELECT
ap.periodname,
a.acctnumber,
a.acctname,
b.department,
SUM(bm.amount) AS budget_amount,
SUM(act.actual_amount) AS actual_amount,
SUM(act.actual_amount) - SUM(bm.amount) AS variance
FROM budgets b
INNER JOIN budgetsmachine bm ON bm.<budget_fk> = b.id
INNER JOIN accountingperiod ap ON ap.id = bm.period
INNER JOIN account a ON a.id = b.account
LEFT JOIN (
SELECT t.postingperiod AS period_id, tal.account AS account_id,
tl.department AS department_id, SUM(tal.amount) AS actual_amount
FROM transactionaccountingline tal
INNER JOIN transactionline tl ON tl.transaction = tal.transaction AND tl.id = tal.transactionline
INNER JOIN transaction t ON t.id = tal.transaction
WHERE t.posting = 'T' AND tal.posting = 'T'
GROUP BY t.postingperiod, tal.account, tl.department
) act
ON act.period_id = bm.period
AND act.account_id = b.account
AND act.department_id = b.department
GROUP BY ap.periodname, a.acctnumber, a.acctname, b.department
PASS: a budget-vs-actual grid returns, and spot-checked cells match the NetSuite UI Budget vs. Actual report. FAIL modes and what each means: (a) parser rejects the derived table -> rewrite without the subquery, or accept two separate pulls joined client-side, which is still Path A, just less elegant; (b) rows return but budget and actual columns never co-populate -> grain mismatch (budget at department only, actuals at department+class+project). This is the expected realistic failure and it is a data problem, not an API problem. It matches the parent brief's named "budget grain below actual grain" landmine, and the fix is to aggregate actuals up to the budget's grain, not to push the API harder; (c) HTTP 5xx or connection reset after a long wait -> record the elapsed time, that is the timeout number nobody has published.
Step 5 - Ceiling probe (measure, do not assume)
Run the Step 3 statement without GROUP BY (raw transaction lines) against the largest available fiscal year, then:
limit=1000, offset=0. Record wall-clock milliseconds.- Walk
offsetin increments of 1000 (offset must be divisible by limit [DOC]). Record wall clock per page and note whether latency degrades with offset depth. - At
offset=99000, then attemptoffset=100000. Capture the exact error string. This is the empirical test of Oracle's 100,000-result statement. - Separately, run one deliberately expensive statement (wide column list, no
WHERE, multi-table join) and record either its completion time or its failure mode and elapsed time. This is the only way to produce the timeout number, which no source publishes.
Record all timings in a table and file them back into this brief. A single number is not enough - report p50 and worst-case page latency, because the adapter's retry and chunking policy depends on the tail.
Outcome routing
| Test result | Path selected |
|---|---|
| Steps 1-4 PASS, Step 5 confirms the aggregated pull is well under 100k and completes in acceptable wall clock | Path A - native SuiteQL/REST pull. Build the adapter against the canonical intermediate schema. NetSuite becomes a same-tier-as-Intacct integration with more code. |
| Step 1 or 2 FAILS (permission-blocked, table absent, or annual-only grain), Step 3 PASSES | Path B - transaction-line reconstruction. NetSuite adapter delivers actuals only; budget is a separate integration (Excel or planning tool). Scope and price as two integrations. |
| Step 4 FAILS on grain mismatch only | Path A with a grain-normalization layer. Not a path change; add an explicit aggregate-to-budget-grain step and surface the lost granularity to the client during scoping. |
| Step 5 shows the aggregated pull exceeds 100k rows or times out | Path C - SuiteAnalytics Connect (paid ODBC). Client-side license dependency. Becomes a commercial gate in the SOW, not an engineering task. |
Why this is in the vault
This is the blocking dependency for the ab-managerial-accounting brigade's NetSuite adapter: it converts an untested scoping assumption inherited from [[2026-07-19-midmarket-variance-analysis-data-inputs]] into a documented capability floor (SuiteQL over REST is first-party documented, budget tables are practitioner-attested, the row ceiling is 100,000 without a paid Connect license) plus a runnable test that routes the build between three concretely different adapter architectures and two different SOW shapes. It also establishes the pre-test design decision - aggregate server-side in SuiteQL, never client-side - which is actionable on the adapter build today without waiting for an account.
Open follow-ups
- Run the test plan above against a live NetSuite account. This is the top item and the reason this brief exists. Priority sourcing route: ask any NetSuite-running prospect or client for a read-only sandbox role, since that conversation also answers whether their budget lives in NetSuite at all.
- What is NetSuite's actual SuiteQL request timeout over REST? No named source publishes a number. Only Step 5 of the test plan produces it, and it is currently the largest unquantified risk in the NetSuite adapter estimate.
- Verify the Oracle page "Managing SuiteQL Queries in Planning and Budgeting Sync" (docs.oracle.com/.../article_161554610.html). If Oracle's own Planning & Budgeting sync transports budget data over SuiteQL, budget-table access becomes first-party supported rather than community-attested, which would upgrade the confidence label from MEDIUM-HIGH to HIGH without needing an account.
- Are
budgetimportandbudgetexchangerate(or their current equivalents) exposed to SuiteQL, and doesbudgetexchangeratematter for multi-currency clients? Unresolved. Relevant to any client consolidating across subsidiaries, where the parent brief already flags FX translation-vs-transaction as an ugly join. - Does SuiteQL support
WITH/ CTEs, and what is the practical column-count ceiling per statement? Practitioner reports are inconsistent and no primary source was found. Determines whether the adapter's statements can be readable or must be nested subqueries. - What does the NetSuite Professionals community archive thread on SuiteQL REST pagination caps actually say? Returned HTTP 403 for this brief. Anyone with community access should read it before running Step 5, it may pre-answer the offset-cap behavior.
- Does the 100,000-result cap apply per-statement or per-integration-per-window? Oracle's wording is per-query but does not say so unambiguously, and the distinction changes whether a multi-year backfill is chunked by period or blocked outright.
Related
- [[2026-07-19-midmarket-variance-analysis-data-inputs]] — the parent brief that raised this exact question as follow-up #2; supplies the three-tier ERP automatability framing and the canonical intermediate schema
- [[2026-03-28-cfosecrets-unbundling-the-erp-tech-legacy-iv]] — the 4-layer unbundled-ERP thesis; this brief is a Layer 3 integration detail underneath it
- [[2026-04-28-cfosecrets-finance-stack-of-the-future-unbundled-erp]] — companion unbundled-ERP framing
- [[variance-analysis-ticket]] — the shipped standard-costing brigade ticket; the FP&A sibling skill this adapter would feed does not exist yet
- [[cma-variance-core-competency]] — the standard-costing taxonomy whose inputs a GL pull cannot supply; the contrast case for why the budget-vs-actual grain question matters
Sources
Vault
~/rdco-vault/06-reference/research/2026-07-19-midmarket-variance-analysis-data-inputs.md~/rdco-vault/06-reference/2026-03-28-cfosecrets-unbundling-the-erp-tech-legacy-iv.md~/rdco-vault/06-reference/2026-04-28-cfosecrets-finance-stack-of-the-future-unbundled-erp.md~/rdco-vault/08-tooling/brigade-rail/variance-analysis-ticket.md~/rdco-vault/10-source-material/competencies/variance-analysis/cma-variance-core-competency.md
Web (fetched and read)
- Oracle NetSuite — Executing SuiteQL Queries Through REST Web Services: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_157909186990.html (endpoint path,
Prefer: transient, 100,000-result cap) - Oracle NetSuite — Collection Paging: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_156414087576.html (1000/page, 1000 pages, offset-divisible-by-limit rule)
- Tim Dietrich — NetSuite: Use SuiteQL to Access Budget Information (2023-07-12): https://timdietrich.me/blog/netsuite-suiteql-budgets/ (
Budgets+BudgetsMachinetables, column names, "Transactions - Set Up Budgets" permission, explicit deferral of the actuals join)
Web (surfaced in search, NOT fetched — claims limited to titles)
- Oracle NetSuite — Managing SuiteQL Queries in Planning and Budgeting Sync: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/article_161554610.html
- Oracle NetSuite — Budget vs. Actual Report: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_N2097218.html
- Oracle NetSuite — Budget vs. Actual Workbook: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_161797020296.html
- Tim Dietrich — SuiteQL to Access Journal Entries and Determine GL Impact: https://timdietrich.me/blog/netsuite-suiteql-journal-entries-gl-impact/
Access-blocked (flagged, not retried)
- NetSuite Professionals archive — SuiteQL REST pagination page cap thread: https://archive.netsuiteprofessionals.com/t/10469738/ — HTTP 403 Forbidden
Explicitly excluded from evidence
- Coefficient.io use-case pages on NetSuite budget-vs-actual. Vendor-marketing content with a commercial interest in the conclusion; SQL fragments shown are incomplete and not self-consistent. Noted only as absence-of-contradiction.
Verification note: No query in this brief was executed against a live NetSuite instance. RDCO has no NetSuite account as of 2026-07-26. All SuiteQL statements above are constructed from documentation and a 2023 practitioner post; table names, column names, and foreign-key names are unverified against the current Records Catalog and Step 0 of the test plan exists specifically to correct them. The 100,000-result ceiling is Oracle-primary and quoted verbatim. No timeout figure appears in this brief because no named source publishes one.