What comes back
Does BUILTIN.DF silently drop rows in SuiteQL?
Short answer
No. Wrapping a column in BUILTIN.DF returned the same row count as the bare table in every form tested. The gap people see is COUNT(BUILTIN.DF(col)) skipping nulls, which is ordinary COUNT(expr) behaviour, not a row drop.
No. Wrapping a column in BUILTIN.DF returned the same row count as the bare table in every form tested. The number that looks like a drop is COUNT(BUILTIN.DF(col)), which skips nulls, as COUNT(expr) does in any SQL.
Minimal reproduction
Count the table:
SELECT COUNT(*) AS n FROM transaction
Then count it again through an inline view that wraps a column in BUILTIN.DF:
SELECT COUNT(*) AS n FROM (SELECT id, BUILTIN.DF(entity) AS x FROM transaction)
Identical values. The community claim is usually reported with the function in a WHERE clause rather than an inline view, so that form was tested too:
SELECT COUNT(*) AS n FROM transaction WHERE BUILTIN.DF(entity) IS NOT NULL
SELECT COUNT(*) AS n FROM transaction WHERE BUILTIN.DF(entity) IS NULL
Those two complementary predicates summed to the unfiltered COUNT(*) exactly, with nothing left over. A null entity goes in, a null comes out, and the row stays. The IS NULL count also matched WHERE entity IS NULL on the raw column to the row.
Now the query that starts the rumour:
SELECT COUNT(BUILTIN.DF(entity)) AS n FROM transaction
Lower than COUNT(*), and lower by precisely the number of rows where entity is null. Nothing was dropped. The rows are still in the result set, as the two WHERE counts prove.
The fix
If you want rows, count rows with COUNT(*). COUNT(BUILTIN.DF(col)) is not a row count: it counts the rows whose reference resolves to a non-null display value. To account for the gap, count the nulls on the raw column:
SELECT COUNT(*) AS n FROM transaction WHERE entity IS NULL
COUNT(*) minus COUNT(BUILTIN.DF(entity)) came to exactly that number.
Why this happens
COUNT(*) counts rows. COUNT(expr) counts non-null values of the expression. BUILTIN.DF on a null reference produces a null, so the two counts differ by the null population, and someone comparing them concludes the function ate their data. No error is raised at any point, which is why the wrong conclusion survives.
That the mechanism is ordinary COUNT semantics rather than anything NetSuite-specific is inference from the arithmetic. What the run establishes directly is the arithmetic: the same count through an inline view, complementary predicates summing back to the whole table, and a gap equal to the null count.
How to tell if you are affected
Run two statements and compare the two numbers yourself. A single aggregate query cannot answer this: if rows really were dropped, every aggregate inside it would be computed over the already shrunken set and the arithmetic would still balance.
First the bare count:
SELECT COUNT(*) AS n FROM transaction
Then the same count with your column wrapped:
SELECT COUNT(*) AS n FROM (SELECT id, BUILTIN.DF(entity) AS x FROM transaction)
Equal numbers mean no row was dropped, and the gap you were chasing is COUNT(expr) skipping nulls. A smaller second number is a genuine drop, and a case this run did not reproduce. Substitute your own table and column, and pick a column that actually has nulls in it: on a column with no null values the pair proves nothing either way.
Scope
Four field and table combinations: entity, type and status on transaction, and subsidiary on transactionLine. None changed a row count. Each was compared against a COUNT(*) baseline of its own table, re-run for that section rather than carried over: one baseline for transaction, one for transactionLine, and a separate one for the entity case in its own block.
Two of the four combinations cannot distinguish "DF keeps nulls" from "there was nothing to keep": status on transaction and subsidiary on transactionLine had no null values at all in the account tested. The null population of type was not measured. entity, which does have nulls, is what carries that part of the result.
One production account on NetSuite 2026.1. Every probe behind this page ran through N/query.runSuiteQL; not one was run against the REST endpoint, so the finding is untested there. It also does not rule out the behaviour on another version, another field, or inside a join or GROUP BY not tested here. It does establish that the simplest and most direct reproduction does not show the defect.
How this was established
14 probe runs against one live NetSuite 2026.1 account on the Administrator role, through N/query.runSuiteQL. Where this page draws a boundary around a finding, that boundary is the edge of what was actually run.
Related
- Why does SuiteQL return CustInvc instead of Invoice for transaction type?
The type column holds internal codes: CustInvc, VendBill, ExpRept, CuTrSale, RevArrng and so on. A filter written against the display name Invoice matches no row. Use the code, or filter on BUILTIN.DF(type), which selected the same rows.
- Why does totalResults in the SuiteQL REST response not match SELECT COUNT(*)?
totalResults is not a row count. It reports min(rows your query returns, limit x 1000), so on a larger result set it reports the cap and calls it the total, HTTP 200, no flag. Run SELECT COUNT(*) separately.
- Why does my SuiteQL RIGHT JOIN between transaction and transactionLine return 0 rows?
RIGHT JOIN between transaction and transactionLine runs as an inner join and returns zero rows. FULL OUTER is downgraded to a LEFT join from the header. No error. LEFT JOIN from the header and Oracle (+) both work.
From the blog
- NetSuite Saved Search Formulas: What Breaks and Why
Field IDs, NULL propagation, main line, and the difference between Criteria, Results and Summary. The parts of NetSuite saved search formulas that fail quietly, and how to read the failure.