---
title: "dbt unique_combination_of_columns: how to use it | Metacenta"
description: "dbt_utils.unique_combination_of_columns tests that several columns together identify one row. How to set it up, which layout to use, and the other options."
url: "https://metacenta.com/rules/model-grain-not-asserted"
updated: "2026-10-14"
tokens: 1306
---

Testing · rule model-grain-not-asserted

# dbt unique_combination_of_columns: how to use it

`dbt_utils.unique_combination_of_columns` is a generic test from the dbt_utils package. It passes when no two rows share the same values across the columns you list, even when each column repeats on its own.

A Metacenta review checks this under the rule **Tested models assert their grain (a uniqueness test)**. Everything below applies whether or not you ever commission one.

## What the test checks

Take an orders table loaded from several countries. `order_id` repeats across countries and `country_code` repeats across orders, so `unique` on either column fails. The pair is unique, and this test asserts exactly that.

## How to set it up

Install dbt_utils through `packages.yml` and run `dbt deps`.

```yaml
packages:
  - package: dbt-labs/dbt_utils
    version: 1.4.1
```

Then add the test at model level, not under a column, and list the columns under `combination_of_columns`.

```yaml
models:
  - name: fct_orders
    data_tests:
      - dbt_utils.unique_combination_of_columns:
          arguments:
            combination_of_columns:
              - country_code
              - order_id
```

The `arguments:` block is available from dbt 1.10.5. Older versions set `combination_of_columns` directly under the test name. The optional `quote_columns` argument defaults to `false`; set it to `true` when a column name needs quoting.

## Other ways to test a composite key

dbt's docs give two more options. You can build a surrogate key column in the model and test it with `unique`. Or you can pass `unique` an expression through `column_name`.

```yaml
models:
  - name: fct_orders
    data_tests:
      - unique:
          arguments:
            column_name: "(country_code || '-' || order_id)"
```

Both work, but dbt recommends `unique_combination_of_columns` on large datasets, where it performs better.

## What to test alongside it

The test checks that the combination is unique, not that each column is populated. Add `not_null` to each column in the key, which we cover in [dbt key columns: no not_null test](https://metacenta.com/rules/model-no-not-null-test).

On an incremental model, list the same columns as its `unique_key`; see [dbt incremental models: how unique_key works](https://metacenta.com/rules/incremental-without-unique-key). A model with no tests at all is covered by [dbt models with no tests](https://metacenta.com/rules/model-untested).

See [dbt's FAQ on testing two columns](https://docs.getdbt.com/faqs/Tests/uniqueness-two-columns) for the full examples.

## What this rule checks

Among models that have tests, this rule flags those where nothing asserts one row per key. `unique`, `dbt_utils.unique_combination_of_columns` and the dbt_expectations uniqueness tests count. A uniqueness test limited by `where` or `row_condition` does not, because it covers only some rows.

## Why it matters

A duplicate row doubles every sum built on it, and nothing fails when it happens. It's the most common reason a metric can't be reconciled.

## How to fix it

**Test that no key appears twice in any table.** Add `unique` on the key column. For a grain defined by several columns, use `dbt_utils.unique_combination_of_columns` over exactly those columns.

Before:

```yaml
models:
  - name: fct_order_lines
    columns:
      - name: order_id
        data_tests:
          - not_null
```

After:

```yaml
models:
  - name: fct_order_lines
    data_tests:
      - dbt_utils.unique_combination_of_columns:
          arguments:
            combination_of_columns:
              - order_id
              - line_number
```

## When it is fine to leave

Some models have no grain by design, such as an append-only event log where two identical rows are two real events. Say so in the model's description. Models with no tests at all are not judged here; the rule on untested models covers them.

## What we need to check it

`manifest.json` alone. We name any uniqueness test that was left out because of a `where` filter, so you can see why it did not count.

## Published rules it corresponds to

**[dbt_project_evaluator](https://dbt-labs.github.io/dbt-project-evaluator/)**, rule `fct_missing_primary_key_tests`. Ours checks a subset of what it flags; theirs is stricter. dbt Labs' rule requires a `not_null` test as well as a uniqueness one; that half is `model-no-not-null-test` here, so their single rule is these two checks together.

This means our check corresponds to their rule. It does not mean the publisher reviewed or endorses it.

[Ask about a review](https://metacenta.com/enquire?src=rule-model-grain-not-asserted&via=md)

## Related pages

- [dbt models with no tests](https://metacenta.com/rules/model-untested)
- [dbt key columns: no not_null test](https://metacenta.com/rules/model-no-not-null-test)
- [SQL tables with no assertion](https://metacenta.com/rules/sql-relation-untested)
- [Dagster asset checks: how to set them up](https://metacenta.com/rules/dagster-asset-unchecked)
- [A sample review, as a client reads it](https://metacenta.com/sample)
- [All published rules](https://metacenta.com/rules)
- [How a review is assessed](https://metacenta.com/standard)
