Filter Rows by a Value Inside a Comma-Separated Field with parameter

# How to Filter Rows by a Value Inside a Comma-Separated Field (`contains()` Workaround Using `locate()`)

Spent a while troubleshooting this and wanted to share the solution. I’ve seen several threads with the same issue but no complete write-up.

-–

## THE PROBLEM

Common data pattern: you have a text field that stores multiple comma-separated values. Product tags, assigned regions, skill sets — anything where one row can belong to multiple categories.

You want to let dashboard users pick a value from a dropdown and filter to only rows where that comma-separated field includes the selected value.

Natural approach — use `contains()`:

```

ifelse(contains({AssignedRegions}, ${SelectedRegion}), 1, 0)

```

This throws:

> Expression {{argumentName}} for function {{functionName}} has incorrect argument type {{incorrectArgumentType}}.

**Why it fails:** QuickSight’s `contains()` only accepts a string literal as the second argument — not a column reference or parameter. Even wrapping in `toString()` doesn’t fix it. Known limitation.

And even if it did work, `contains()` would give you false positive partial matches. Searching for “US” inside “AUS,EURO” returns true because “US” is a substring of “AUS”.

Two problems. One workaround.

-–

## THE SOLUTION — `locate()` WITH COMMA-WRAPPING

`locate()` accepts both column references and parameters as arguments. The comma-wrapping technique prevents partial matches.

```

ifelse(

locate(

concat(',', replace(coalesce({AssignedRegions}, ''), ' ', ''), ','),

concat(',', replace(coalesce(${SelectedRegion}, ''), ' ', ''), ',')

) > 0,

1,

0

)

```

**What each piece does:**

| Function | Purpose |

|—|—|

| `coalesce({field}, ‘’)` | Null-safe — treats null as empty string |

| `replace(…, ’ ', ‘’)` | Strips spaces so “US, AUS” becomes “US,AUS” for clean matching |

| `concat(‘,’, …, ‘,’)` | Wraps in commas so we search for “,US,” instead of just “US” |

| `locate(haystack, needle)` | Returns position if found (> 0), or 0 if not found |

**Why the comma-wrapping matters:**

Without it, searching for “US” in “AUS,EURO” returns a match because “US” appears inside “AUS”. With comma-wrapping, we search for `,US,` inside `,AUS,EURO,` — no match. But `,US,` inside `,US,AUS,EURO,` — match at position 1. Exact token matching.

-–

## EXAMPLE — Sales Rep Regional Assignment Filter

**Scenario:** You manage a sales team. Each rep is assigned to multiple regions stored in a comma-separated field. Managers want to select a region from a dropdown and see only the reps who cover that region.

**Sample data:**

| RepID | RepName | AssignedRegions | Q1Sales |

|—|—|—|—|

| 1 | Alice Chen | US, EMEA, APAC | 245000 |

| 2 | Bob Martinez | US, LATAM | 189000 |

| 3 | Carol Wu | APAC, EMEA | 312000 |

| 4 | David Kim | LATAM | 97000 |

| 5 | Eva Singh | US, APAC | 278000 |

### Step 1 — Create a Parameter

- **Name:** `SelectedRegion`

- **Type:** String, Single value

- **Default value:** `ALL`

### Step 2 — Create a Control

- Link a dropdown control to the `SelectedRegion` parameter

- Specific values: ALL, US, EMEA, APAC, LATAM

### Step 3 — Create the Calculated Field

Name it `ShowRep`:

```

ifelse(

${SelectedRegion} = ‘ALL’,

1,

ifelse(

locate(

  concat(',', replace(coalesce({AssignedRegions}, ''), ' ', ''), ','),

  concat(',', replace(coalesce(${SelectedRegion}, ''), ' ', ''), ',')

) > 0,

1,

0

)

)

```

The outer `ifelse` handles the “ALL” case — when the user wants to see every rep without filtering.

### Step 4 — Add a Filter to Your Visual

- **Field:** `ShowRep`

- **Condition:** Equals `1`

**Results:**

| Selection | Reps Shown |

|—|—|

| ALL | Alice, Bob, Carol, David, Eva (all reps) |

| US | Alice, Bob, Eva |

| EMEA | Alice, Carol |

| APAC | Alice, Carol, Eva |

| LATAM | Bob, David |

-–

## COPY-PASTE TEMPLATE

For any comma-separated field with a single-value parameter and an ALL option:

```

ifelse(

${YourParameter} = ‘ALL’,

1,

ifelse(

locate(

  concat(',', replace(coalesce({YourCommaField}, ''), ' ', ''), ','),

  concat(',', replace(coalesce(${YourParameter}, ''), ' ', ''), ',')

) > 0,

1,

0

)

)

```

Replace `{YourCommaField}` with your field name and `${YourParameter}` with your parameter name. Drop the ALL check if you don’t need it.

-–

## ADVANCED PATTERN — COMPOSITE SEARCH TOKEN (PARAMETER + FIELD)

The basic pattern searches for a single parameter value inside a comma-separated list. But what if the tokens in your list are compound values — two dimensions joined together — and the search key needs to be built dynamically from a parameter AND a field on the same row?

This comes up when your comma-separated list contains things like “REGION-SEGMENT” or “INSURER-PLANTYPE”, and you need to match on one half from a user selection and the other half from the row’s own data.

**The formula shape changes to:**

```

ifelse(

locate(

concat(',', replace(coalesce({CompoundList}, ''), ' ', ''), ','),

concat(',', replace(coalesce(concat(${SelectedParam}, '-', {RowField}), ''), ' ', ''), ',')

) > 0,

1,

0

)

```

**Key difference:** instead of searching for just `${SelectedParam}`, we search for `concat(${SelectedParam}, ‘-’, {RowField})` — building the needle dynamically from a parameter + a field.

Three real-world scenarios where this applies.

-–

### Scenario 1: Healthcare Provider Network — “Does my doctor accept my plan?”

Insurance dashboard. Each provider has a comma-separated list of accepted plan codes. Each code is a compound of insurer + plan type (e.g., “BLUECROSS-PPO”).

**Sample data:**

| ProviderID | ProviderName | AcceptedPlans | PlanType | Specialty |

|—|—|—|—|—|

| 1 | Dr. Sarah Lee | BLUECROSS-PPO,AETNA-HMO,UNITED-PPO | PPO | Cardiology |

| 2 | Dr. James Park | AETNA-HMO,AETNA-PPO | HMO | Orthopedics |

| 3 | Dr. Maria Santos | BLUECROSS-PPO,BLUECROSS-HMO,UNITED-HMO | PPO | Pediatrics |

| 4 | Dr. Kevin Brown | UNITED-PPO,UNITED-HMO | PPO | Dermatology |

| 5 | Dr. Amy Chen | AETNA-PPO,BLUECROSS-HMO | PPO | Neurology |

**Parameter:** `SelectedInsurer` — values: ALL, BLUECROSS, AETNA, UNITED

**Calculated field `ShowProvider`:**

```

ifelse(

${SelectedInsurer} = ‘ALL’,

1,

ifelse(

locate(

  concat(',', replace(coalesce({AcceptedPlans}, ''), ' ', ''), ','),

  concat(',', replace(coalesce(

    concat(${SelectedInsurer}, '-', {PlanType}),

  ''), ' ', ''), ',')

) > 0,

1,

0

)

)

```

**Results when selecting “BLUECROSS”:**

| Provider | PlanType | Searches For | Match? | Why |

|—|—|—|—|—|

| Dr. Sarah Lee | PPO | `,BLUECROSS-PPO,` | :white_check_mark: | Found in AcceptedPlans |

| Dr. James Park | HMO | `,BLUECROSS-HMO,` | :cross_mark: | Only AETNA plans |

| Dr. Maria Santos | PPO | `,BLUECROSS-PPO,` | :white_check_mark: | Found in AcceptedPlans |

| Dr. Kevin Brown | PPO | `,BLUECROSS-PPO,` | :cross_mark: | Only UNITED plans |

| Dr. Amy Chen | PPO | `,BLUECROSS-PPO,` | :cross_mark: | Has BLUECROSS-HMO, not PPO |

The filter correctly distinguishes between BLUECROSS-PPO and BLUECROSS-HMO even though both start with “BLUECROSS”.

-–

### Scenario 2: Retail Promotion Eligibility — “Which products qualify for this campaign in their category?”

Each product has a list of promo campaigns it qualifies for, stored as compound tokens like “SUMMER2025-ELECTRONICS”. A category manager selects a campaign, and the filter builds the search from the campaign + the product’s own category.

**Sample data:**

| ProductID | ProductName | PromoCampaigns | Category | Price |

|—|—|—|—|—|

| 1 | 4K Smart TV | SUMMER2025-ELECTRONICS,BOGO-ELECTRONICS | ELECTRONICS | 599 |

| 2 | Running Shoes | SUMMER2025-APPAREL,CLEARANCE-APPAREL | APPAREL | 89 |

| 3 | Bluetooth Speaker | BOGO-ELECTRONICS,CLEARANCE-ELECTRONICS | ELECTRONICS | 45 |

| 4 | Winter Jacket | CLEARANCE-APPAREL | APPAREL | 199 |

| 5 | Laptop Stand | SUMMER2025-ELECTRONICS,SUMMER2025-OFFICE | ELECTRONICS | 35 |

**Parameter:** `SelectedCampaign` — values: ALL, SUMMER2025, BOGO, CLEARANCE

**Calculated field `ShowProduct`:**

```

ifelse(

${SelectedCampaign} = ‘ALL’,

1,

ifelse(

locate(

  concat(',', replace(coalesce({PromoCampaigns}, ''), ' ', ''), ','),

  concat(',', replace(coalesce(

    concat(${SelectedCampaign}, '-', {Category}),

  ''), ' ', ''), ',')

) > 0,

1,

0

)

)

```

**Results when selecting “SUMMER2025”:**

| Product | Category | Searches For | Match? |

|—|—|—|—|

| 4K Smart TV | ELECTRONICS | `,SUMMER2025-ELECTRONICS,` | :white_check_mark: |

| Running Shoes | APPAREL | `,SUMMER2025-APPAREL,` | :white_check_mark: |

| Bluetooth Speaker | ELECTRONICS | `,SUMMER2025-ELECTRONICS,` | :cross_mark: |

| Winter Jacket | APPAREL | `,SUMMER2025-APPAREL,` | :cross_mark: |

| Laptop Stand | ELECTRONICS | `,SUMMER2025-ELECTRONICS,` | :white_check_mark: |

Each product is checked against the campaign for its own category, not just whether the campaign name appears anywhere in the string.

-–

### Scenario 3: Sales Territory Overlap — “Which reps cover this region for each customer’s segment?”

Sales reps have territory assignments stored as compound tokens: region + customer segment (e.g., “WEST-ENT” for West region, Enterprise segment). A VP selects a region, and the filter matches against each customer row’s own segment.

**Sample data:**

| RepID | RepName | Territories | CustomerSegment | Q1Pipeline |

|—|—|—|—|—|

| 1 | Alice Chen | WEST-ENT,EAST-ENT,CENTRAL-SMB | ENT | 1200000 |

| 2 | Bob Martinez | WEST-SMB,WEST-MID | SMB | 450000 |

| 3 | Carol Wu | EAST-ENT,EAST-SMB | ENT | 890000 |

| 4 | David Kim | CENTRAL-ENT,CENTRAL-MID | MID | 670000 |

| 5 | Eva Singh | WEST-ENT,CENTRAL-ENT | ENT | 1050000 |

**Parameter:** `SelectedRegion` — values: ALL, WEST, EAST, CENTRAL

**Calculated field `ShowRep`:**

```

ifelse(

${SelectedRegion} = ‘ALL’,

1,

ifelse(

locate(

  concat(',', replace(coalesce({Territories}, ''), ' ', ''), ','),

  concat(',', replace(coalesce(

    concat(${SelectedRegion}, '-', {CustomerSegment}),

  ''), ' ', ''), ',')

) > 0,

1,

0

)

)

```

**Results when selecting “WEST”:**

| Rep | CustomerSegment | Searches For | Match? | Why |

|—|—|—|—|—|

| Alice Chen | ENT | `,WEST-ENT,` | :white_check_mark: | Found in Territories |

| Bob Martinez | SMB | `,WEST-SMB,` | :white_check_mark: | Found in Territories |

| Carol Wu | ENT | `,WEST-ENT,` | :cross_mark: | Only EAST territories |

| David Kim | MID | `,WEST-MID,` | :cross_mark: | Only CENTRAL territories |

| Eva Singh | ENT | `,WEST-ENT,` | :white_check_mark: | Found in Territories |

The filter finds reps who cover the selected region for the specific customer segment on that row — not just any rep who has “WEST” anywhere in their territory list.

-–

## COMPOSITE TOKEN TEMPLATE

```

ifelse(

${YourParameter} = ‘ALL’,

1,

ifelse(

locate(

  concat(',', replace(coalesce({YourCommaField}, ''), ' ', ''), ','),

  concat(',', replace(coalesce(

    concat(${YourParameter}, '-', {YourRowField}),

  ''), ' ', ''), ',')

) > 0,

1,

0

)

)

```

Replace `{YourCommaField}` with the comma-separated list field, `${YourParameter}` with your parameter, and `{YourRowField}` with the row-level field that forms the second half of the compound token. Adjust the delimiter (`-`) to match whatever separator your data uses.

-–

## WHY NOT JUST USE `contains()`?

| Issue | `contains()` | `locate()` + comma-wrapping |

|—|—|—|

| Accepts parameters | :cross_mark: String literals only | :white_check_mark: Parameters and field references |

| Exact token matching | :cross_mark: Partial substring matches | :white_check_mark: Comma-wrapping prevents false positives |

| Null safety | :cross_mark: | :white_check_mark: Via `coalesce()` |

| Dynamic composite keys | :cross_mark: | :white_check_mark: Build needle with `concat()` |

-–

Hope this saves someone the hours I spent on it. Happy to answer questions.

**Related threads:**

- [contains() function using two columns]( Calculated field - "Contains" function using two columns )

- [contains() + parameter incorrect argument type]( Help with contains() + parameter in calculated field — “incorrect argument type” error )

- [Filter by a string separated by commas](Filter by a String separated by commas / "Contains" function argument type)

1 Like