# Create a Computed Join

Source: https://userguide.qualytics.io/container/computed-join/how-tos/create-a-computed-join/

This page documents the **Add Computed Join** form, then walks through the steps to create one from the datastore container list. For the conceptual overview, see [How Computed Join Works](https://userguide.qualytics.io/container/computed-join/deep-dive/how-it-works/).

The form has two editor modes. **Pairwise** joins two containers on a single key. **SQL** joins two or more aliased sources with a Spark SQL query you write. The **Editor Mode** tabs switch between them, and the fields below the tabs change with the mode.

> **Info: Permissions**
>
> You need **Editor** permission on the parent datastore, or **Author** permission plus ownership of the new container, and at least **Viewer** permission on every other datastore an input reads from. The parent datastore is the left container's datastore in pairwise mode, or the first source's datastore in SQL mode. See [Computed Join Permissions](https://userguide.qualytics.io/container/computed-join/deep-dive/permissions/) for the full matrix.

> **Tip: Show me how**
>
> The app can walk you through this. Click **Show me how**  in the form's header, or press `H` while it is open, and the **Add a Computed Join** walkthrough highlights each step while you fill in the real form. See [The How-tos Scope](https://userguide.qualytics.io/using-the-platform/web-app/global-search/command-palette/#the-how-tos-scope) for every way to start one.

## Field reference

The **Add Computed Join** form starts with the fields every join has, then shows the fields for the selected **Editor Mode**, and ends with the metadata fields.

### Common fields

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Name** | Yes | Text | Unique name for the Computed Join container on the parent datastore. All spaces are replaced by underscores. Max 255 characters. |
| **Editor Mode** | No | Tabs | **Pairwise** (default) or **SQL**. Pick the tab below for the fields each mode shows. |

> **Warning: Mode is fixed after save**
>
> The Editor Mode tabs are locked once the join is saved. To switch from pairwise to SQL mode (or the reverse), you must delete the join and create a new one. Deletion is destructive: the join's history, quality checks scoped to it, and anomalies detected on it are removed with the container. Rebuild those artifacts on the new join.

### Mode fields

**Tab: Pairwise**

Pairwise mode joins a left and a right container on one key each. Every field below is required except the two prefixes and the Filter and Group By clauses.

**Join Type**

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Join Type** | Yes | Option | One of **Inner Join**, **Left Join**, **Right Join**, or **Full Outer Join**. Defaults to Inner Join. |

**Left Reference**

The second field's label follows the type of the left datastore: **Table** for schema-based datastores and **File** for file-based datastores.

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Datastore** | Yes | Option | Locked to the datastore you opened. The Computed Join lives under this datastore. |
| **Table** or **File** | Yes | Option | Profiled container to join from the left side: table, view, file, Computed Table, or Computed File. |
| **Field** | Yes | Option | The column used as the join key on the left side. Must resolve to the same data type as the right **Field**; mismatched types surface as a validation error when you click **Validate** or **Save**. |
| **Prefix** | No | Text | Prefix applied to every column from the left side in the joined result. Defaults to `left`. Max 100 characters. |

**Right Reference**

The second field's label reads **Container** until a right datastore is selected, then follows that datastore's type in the same way as the left side.

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Datastore** | Yes | Option | Datastore for the right container. Can be the same as the left datastore or a different one. You need at least **Viewer** permission on it. |
| **Table**, **File**, or **Container** | Yes | Option | Profiled container to join from the right side. Same supported types as the left side. |
| **Field** | Yes | Option | The column used as the join key on the right side. Must resolve to the same data type as the left **Field**. |
| **Prefix** | No | Text | Prefix applied to every column from the right side. Defaults to `right`. Max 100 characters. Should differ from the left **Prefix** when both sides share column names. Identical prefixes trigger an ambiguous column reference error at **Validate**. |

**Select Expression, Filter Clause, and Group By Clause**

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Select Expression** | Yes | Text | SQL select clause naming the columns to include in the joined result. Auto-fills with every top-level field from both sides, prefixed. Edit it to keep only what you need, add aliases, or include expressions. |
| **Filter Clause** | No | Text | Post-join `WHERE` predicate applied to the joined result. |
| **Group By Clause** | No | Text | Group By columns when the **Select Expression** uses aggregation functions. Every non-aggregated column in the **Select Expression** must appear in this clause. |

**Tab: SQL**

SQL mode replaces the Join Type, Left/Right Reference, Select Expression, Filter Clause, and Group By Clause with a **Sources** list and a **Query** editor.

**Sources**

A SQL join reads from 2 to 10 sources. Each source row has these fields:

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Add New Source** | No | Button | Adds a source row. The form starts with the 2 required sources and accepts up to **10**; at 10 the button is disabled and its tooltip reads "A computed join supports at most 10 sources". Once more than 2 sources exist, every source header shows a remove icon. |
| **Datastore** | Yes | Option | The datastore the source container lives in. Sources may span datastores; you need at least **Viewer** permission on each. The Computed Join is created under the first source's datastore. |
| **Table**, **File**, or **Container** | Yes | Option | The container to read. The label reads **Container** until the source's datastore is selected, then follows that datastore's type. It must already be profiled. Base tables, views, files, Computed Tables, and Computed Files are all accepted; another Computed Join is not. |
| **Alias** | Yes | Text | The name the query uses to reference this source. Must start with a letter or underscore, contain only letters, digits, and underscores, be unique within the join, and not clash with a CTE name in the query. |
| **Filter Clause** | No | Text | A Spark SQL `WHERE` expression applied while the source is read, before the query runs. Filtered-out rows are never loaded, so this is the main lever for large sources. |

**Query**

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Query** | Yes | Text | A single Spark SQL statement that references the aliases as tables. Supports composite join keys, CTEs, `GROUP BY`, window functions, and joins across any number of sources. Reference sources by their alias only: qualified or catalog table names, statements that modify data, and arbitrary-code functions (`reflect`, `java_method`) are rejected. |

### Description, Owner, and Additional Metadata

These fields are the same in both modes.

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| **Description** | No | Text | Free-text description of the Computed Join, with Markdown formatting (max 8000 characters). Surfaces alongside the container in lists and search results. The form can auto-suggest a description from the join configuration. |
| **Owner** | No | Option | The user who owns the Computed Join. Defaults to the user creating the join. Only Editors can pick a different owner at create time; Authors are locked to themselves. |
| **Additional Metadata** | No | Key-value | Key-value metadata attached to the container. Keys also act as default values for [runtime variables](https://userguide.qualytics.io/operations/scan/how-tos/use-runtime-variables/) in this container's checks. |

## Steps

**Step 1:** Open the datastore where the Computed Join will live and click **Add**.

**Step 2:** Choose **Computed Join** from the dropdown. The **Add Computed Join** modal opens in pairwise mode.

**Step 3:** Enter a **Name**. Under **Editor Mode**, keep **Pairwise** or select the **SQL** tab.

**Step 4:** Fill in the fields for the selected mode. See the [Field reference](https://userguide.qualytics.io/container/computed-join/how-tos/create-a-computed-join/#field-reference) above for what each field holds.

**Step 5:** Click **Validate**. A `Validation Successful` message appears if the SQL parses and the schema resolves against the input containers.

**Step 6:** Click **Save**. A success message appears, the modal closes, and the new Computed Join appears in the datastore's container list.

*Last updated: October 6, 2026.*
