What comes back

Does BUILTIN.DF silently drop rows in SuiteQL?

Updated
suiteqlbuiltin-dfcountnullsrefutationsilent-failure

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

From the blog