NetSuite Saved Search: Exclude SO if Any Line Matches
Line-level criteria cannot drop a whole sales order when one line matches. Here is the summary filter that removes every line from that order.

On this page
You have a sales order line saved search, and you need a filter that works at the order level. If any line on a given SO matches a condition, every line from that order should disappear from the results. The standard line-level criteria won't do this, because NetSuite evaluates each line independently. A line that doesn't match stays in the results even when a sibling line on the same order does.
This is a classic "group-level exclusion" problem. The good news: you have several workable options. The bad news: none of them are a single checkbox on the Criteria tab.
Why Line-Level Criteria Won't Work
Transaction saved searches evaluate criteria against each line record. When you add a condition like "Item: Name is Widget A," NetSuite filters out individual lines that don't match. Lines that do match remain visible, alongside every other line on the order that happens to pass the filter.
What you actually need is a summary-level exclusion. You want NetSuite to look at the order as a whole, count how many lines match your condition, and then exclude the entire order if that count is greater than zero.
Option 1: Summary Saved Search (The Built-In Approach)
NetSuite's summary saved search can do this, but it changes your output structure. Here's the trade-off:
- Go to Reports > Saved Searches > All Saved Searches > New and select Transaction
- Set the Type filter to Sales Order
- Set Main Line to No to restrict to line-level records
- Click Set Summary at the top of the Criteria subtab
- Set Summary Type to Count and Summary Field to Line ID
- Add your line-level condition as a summary criterion
The key insight: you're counting lines that match your condition, then filtering on that count. Set a Summary Criterion where Count (Line ID) is equal to 0. This excludes any SO where at least one line matches.
The catch is that summary searches return one row per transaction, not per line. You lose the line-level detail in the immediate output. Drill-down still works, but if you need all lines visible in the search results themselves, this approach won't satisfy you.
Option 2: The Running Subtotal Trick (Export Only)
There's a clever formula approach using a running subtotal. The idea:
- Add a Formula (Numeric) column that returns
1when a line matches your condition,0otherwise - Use a Running Total summary to sum those values across all lines of the order
- Export to Excel and filter out orders where the running total is greater than
0
Here's the formula for the column:
CASE WHEN {item} = 'Widget A' THEN 1 ELSE 0 ENDSet the Summary Type for this column to Sum and check Running Total. Every line on an order with at least one matching line will show a running total of 1 or higher.
The problem: you can't put this formula on the Criteria tab. NetSuite won't let you filter a saved search on a formula that references summary-level aggregation. You're stuck exporting and filtering externally.
Option 3: SuiteQL with a Subquery (The Real Fix)
If you need instant results and line-level detail in a single query, SuiteQL is the cleanest solution. The pattern uses a subquery to identify orders with matching lines, then excludes them from the main result set.
SELECT
tl.transaction_id AS "Document Number",
tl.line AS "Line Number",
tl.item AS "Item",
tl.amount AS "Amount"
FROM
transactionline tl
WHERE
tl.transaction_type = 'SalesOrd'
AND tl.main_line = 'F'
AND tl.transaction_id NOT IN (
SELECT
tl2.transaction_id
FROM
transactionline tl2
WHERE
tl2.transaction_type = 'SalesOrd'
AND tl2.main_line = 'F'
AND tl2.item = 'Widget A'
)
ORDER BY
tl.transaction_id, tl.lineThe NOT IN subquery identifies every sales order that has at least one line matching your condition. The outer query then pulls all lines from orders that don't appear in that exclusion list.
This runs instantly, returns full line-level detail, and requires no workflow, script, or custom field on the sales order record. It's the least invasive option if you can run queries outside the saved search UI.
Option 4: Header-Level Custom Field with Scheduled Script
If you need this as a native saved search (not SuiteQL) and the time delay is acceptable, you can populate a header-level field on the SO:
- Create a custom field on the Sales Order record (ID like
custbody_exclude_from_search) - Deploy a scheduled SuiteScript that runs every 15-30 minutes
- The script checks all open sales orders for matching lines and sets the field to
T - Add
custbody_exclude_from_searchisFto your saved search criteria
Here's a governance-safe scheduled script:
/**
* @NApiVersion 2.1
* @NScriptType ScheduledScript
*/
define(['N/search', 'N/record'], (search, record) => {
const execute = (scriptContext) => {
const soSearch = search.create({
type: search.Type.SALES_ORDER,
filters: [
['mainline', 'is', 'F'],
'AND',
['item', 'is', 'Widget A']
],
columns: ['internalid']
});
const orderIds = new Set();
soSearch.run().each((result) => {
orderIds.add(result.getValue({ name: 'internalid' }));
return true;
});
orderIds.forEach((id) => {
record.submitFields({
type: record.Type.SALES_ORDER,
id: id,
values: { 'custbody_exclude_from_search': true }
});
});
};
return { execute };
});The gotcha here: this only updates when the script runs. A new SO created between runs won't be flagged until the next cycle. If your business needs instant exclusion, this approach won't cut it.
What About the Grouping Trick?
Some users try using summary criteria while keeping non-summarized results by grouping on the same record. In practice, this doesn't work reliably in NetSuite's saved search engine. When you set a summary criterion, NetSuite forces the search into summary mode, which collapses your results to one row per group. You can't have both summary-level filtering and line-level output in the same saved search.
Recommendation
Start with the summary saved search if you only need to see which orders are affected, not every line. The drill-down gives you line detail on demand.
If you need full line-level output, go straight to SuiteQL. The NOT IN subquery pattern handles this cleanly without any custom fields, workflows, or scripts. It's instant, accurate, and requires zero maintenance. Check the SuiteScript Records Guide if you need to verify field names or record types in your query.
The scheduled script approach is a fallback for when you're locked into the saved search UI and can tolerate a delay. It works, but it adds moving parts to your environment. That's the kind of thing that will bite you during upgrades if you forget to redeploy the script.
For most real-world scenarios, SuiteQL is the right answer. It solves the problem at the query level, where it belongs. If you're already running SuiteQL queries for reporting or integrations, this pattern will fit right into your existing toolkit.


