Joins and reconciliation

How do I filter SuiteQL records by a multi-select employee field?

Updated
suiteqlmulti-selectmap-tablejoins

Short answer

Do not filter the displayed multi-select column as a scalar value. Filter the generated MAP_ relationship table, then use its record IDs in the main query.

Filter the relationship table, not the multi-select column on the record. The main query should ask for records whose internal ID appears in a subquery against the generated MAP_ table.

The example below is synthetic. It uses an invented customer field and invented employee ID to show the structure without reproducing a customer query or exposing account-specific identifiers.

A multi-select field is not a string column to search with LIKE

Imagine a custom multi-select field called custentity_reviewers on Customer. It contains the employees who may review that customer. A first attempt might treat the field as if it were a text value containing a list of IDs:

SELECT
    c.id,
    c.entityid,
    c.companyname
FROM Customer c
WHERE c.isinactive = 'F'
  AND c.custentity_reviewers LIKE '%48127%'

The query is asking Customer for a text match. That is the wrong model for a multi-select relationship. It also makes the result dependent on how the value is represented, rather than on whether a relationship row exists.

The symptom is usually a missing record, an unsupported filter, or a query that appears to work until the field contains more than one selected employee.

The MAP_ approach is not the only option. SuiteQL also exposes BUILTIN.MNFILTER, which is designed specifically for filtering multi-select fields.

For the same synthetic field, the compact form is:

SELECT
    c.id,
    c.entityid,
    c.companyname
FROM Customer c
WHERE c.isinactive = 'F'
  AND BUILTIN.MNFILTER(
      c.custentity_reviewers,
      'MN_INCLUDE',
      '',
      'TRUE',
      '48127'
  ) = 'T'

Here, MN_INCLUDE asks whether employee 48127 is included in the multi-select field. The exact argument pattern should be checked against the Oracle documentation and the account's query behavior before it is copied into production code.

This is the approach many people find first because it is short and purpose-built. It is a good fit when the question is simply whether one value is present. It does not expose the relationship rows for a count, a join, or additional relationship-level conditions.

The MAP_ form is longer, but makes the relationship explicit:

SELECT
    c.id,
    c.entityid,
    c.companyname
FROM Customer c
WHERE c.isinactive = 'F'
  AND c.id IN (
      SELECT m.mapone
      FROM MAP_customer_custentity_reviewers m
      WHERE m.maptwo = 48127
  )

Use BUILTIN.MNFILTER for a focused membership predicate. Use the MAP_ relationship when the query needs relational operations or when the built-in function is unavailable, rejected, or too opaque to debug.

Find the relationship table when you need the lower-level form

For the synthetic field in this example, assume the generated relationship table is:

MAP_customer_custentity_reviewers

The relationship uses two columns:

  • mapone: the customer internal ID;
  • maptwo: the selected employee internal ID.

To find customers where employee 48127 is one of the selected reviewers, query the relationship directly:

SELECT m.mapone
FROM MAP_customer_custentity_reviewers m
WHERE m.maptwo = 48127

This returns customer IDs, not customer names. That is exactly what the outer query needs.

Use the MAP_ result as the filter for one main query

The corrected pattern is a single query with an IN subquery:

SELECT
    c.id,
    c.entityid,
    c.companyname
FROM Customer c
WHERE c.isinactive = 'F'
  AND c.id IN (
      SELECT m.mapone
      FROM MAP_customer_custentity_reviewers m
      WHERE m.maptwo = 48127
  )

There is no UNION, no scalar comparison against the multi-select field, and no attempt to parse a rendered string. The subquery answers one narrow question: does this customer have employee 48127 among its selected reviewers?

The outer query then applies normal record filters and returns the customer columns needed by the caller.

Why this shape is useful in real automation

A map-table filter keeps the relationship logic separate from the record-selection logic. That makes the query easier to adapt when the caller changes from a customer list to a count, an export, or a join to another record type.

For example, the same relationship filter can become a count without changing the membership test:

SELECT COUNT(*) AS matching_customers
FROM Customer c
WHERE c.isinactive = 'F'
  AND c.id IN (
      SELECT m.mapone
      FROM MAP_customer_custentity_reviewers m
      WHERE m.maptwo = 48127
  )

It can also be used with EXISTS when the outer query already has a more complex customer condition:

SELECT
    c.id,
    c.entityid
FROM Customer c
WHERE c.isinactive = 'F'
  AND EXISTS (
      SELECT 1
      FROM MAP_customer_custentity_reviewers m
      WHERE m.mapone = c.id
        AND m.maptwo = 48127
  )

The IN version is often the clearest starting point because the map query visibly produces the IDs consumed by the outer query. EXISTS expresses the same membership test while correlating the map row to the current customer.

The part that cannot be copied blindly

The field and map-table names in this answer are invented. In an actual account, confirm all three pieces before adapting the pattern:

  1. the record type containing the multi-select field;
  2. the generated MAP_ table name;
  3. which map column points to the record and which points to the selected employee.

Do not assume that every multi-select field exposes the same table name or column direction. The reusable idea is the relationship filter, not the literal identifiers in this example.

The original failure mode was verified in a vendor-approver case, but this page deliberately uses a different record, field, query shape, and purpose so the example teaches the adaptation rather than reproducing the source query.

Related