> ## Documentation Index
> Fetch the complete documentation index at: https://docs.maia.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Join

export const Quiz = ({questions}) => {
  const [answers, setAnswers] = React.useState({});
  const handleAnswer = (qIndex, oIndex) => {
    if (answers[qIndex] !== undefined) return;
    setAnswers(prev => ({
      ...prev,
      [qIndex]: oIndex
    }));
  };
  const answeredCount = Object.keys(answers).length;
  const correctCount = questions.filter((q, i) => answers[i] === q.correct).length;
  const allAnswered = answeredCount === questions.length;
  return <div style={{
    marginBottom: '28px'
  }}>
      {questions.map((q, qIndex) => {
    const chosen = answers[qIndex];
    const isAnswered = chosen !== undefined;
    return <div key={qIndex} style={{
      background: 'var(--colors-background-light, #f9fafb)',
      border: '1px solid var(--colors-border-default, #e5e7eb)',
      borderRadius: '12px',
      padding: '20px 24px',
      marginBottom: '12px'
    }}>
            <p style={{
      fontWeight: '600',
      marginBottom: '12px',
      marginTop: '0'
    }}>
              {qIndex + 1}. {q.question}
            </p>
            <div style={{
      display: 'flex',
      flexDirection: 'column',
      gap: '8px'
    }}>
              {q.options.map((option, oIndex) => {
      const isChosen = chosen === oIndex;
      const isCorrect = oIndex === q.correct;
      let bg = 'transparent';
      let border = '1px solid var(--colors-border-default, #e5e7eb)';
      let color = 'inherit';
      let cursor = isAnswered ? 'default' : 'pointer';
      let icon = null;
      if (isAnswered) {
        if (isCorrect) {
          bg = '#dcfce7';
          border = '1px solid #bbf7d0';
          color = '#15803d';
          icon = '✅';
        } else if (isChosen) {
          bg = '#fee2e2';
          border = '1px solid #fca5a5';
          color = '#b91c1c';
          icon = '❌';
        }
      }
      return <button key={oIndex} onClick={() => handleAnswer(qIndex, oIndex)} style={{
        display: 'flex',
        alignItems: 'center',
        gap: '8px',
        background: bg,
        border,
        borderRadius: '8px',
        padding: '10px 14px',
        color,
        cursor,
        fontSize: '0.9rem',
        textAlign: 'left',
        width: '100%',
        transition: 'background 0.15s'
      }}>
                    <span style={{
        fontWeight: '600',
        minWidth: '20px'
      }}>
                      {String.fromCharCode(65 + oIndex)}.
                    </span>
                    <span style={{
        flex: 1
      }}>{option}</span>
                    {icon && <span>{icon}</span>}
                  </button>;
    })}
            </div>
          </div>;
  })}

      {allAnswered && <div style={{
    background: correctCount === questions.length ? '#dcfce7' : correctCount >= questions.length / 2 ? '#fef9c3' : '#fee2e2',
    border: `1px solid ${correctCount === questions.length ? '#bbf7d0' : correctCount >= questions.length / 2 ? '#fde68a' : '#fca5a5'}`,
    borderRadius: '12px',
    padding: '16px 24px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    gap: '16px'
  }}>
          <span style={{
    fontWeight: '600',
    fontSize: '1rem'
  }}>
            {correctCount === questions.length ? `🎉 Perfect score! ${correctCount}/${questions.length} correct.` : correctCount >= questions.length / 2 ? `👍 ${correctCount}/${questions.length} correct. Review the highlighted answers above.` : `📖 ${correctCount}/${questions.length} correct. Take another look at the highlighted answers above.`}
          </span>
          <button onClick={() => setAnswers({})} style={{
    background: 'white',
    border: '1px solid var(--colors-border-default, #e5e7eb)',
    borderRadius: '8px',
    padding: '6px 14px',
    fontSize: '0.85rem',
    fontWeight: '500',
    cursor: 'pointer',
    whiteSpace: 'nowrap'
  }}>
            Try again
          </button>
        </div>}
    </div>;
};

export const ComponentMetadata = ({warehouses, unsupportedWarehouses = [], componentType, connectionInputs, connectionOutputs}) => {
  const allWarehouses = [...warehouses.map(w => ({
    name: w,
    supported: true
  })), ...unsupportedWarehouses.map(w => ({
    name: w,
    supported: false
  }))];
  return <div style={{
    background: 'var(--colors-background-light, #f9fafb)',
    border: '1px solid var(--colors-border-default, #e5e7eb)',
    borderRadius: '12px',
    padding: '20px 28px',
    marginBottom: '28px',
    boxShadow: '0 1px 4px rgba(0,0,0,0.10)'
  }}>
      <table style={{
    width: '100%',
    borderCollapse: 'collapse'
  }}>
        <tbody>
          <tr>
            <td style={{
    fontWeight: '600',
    paddingRight: '32px',
    paddingBottom: '14px',
    whiteSpace: 'nowrap',
    verticalAlign: 'middle',
    width: '180px'
  }}>Project Availability</td>
            <td style={{
    paddingBottom: '14px',
    verticalAlign: 'middle'
  }}>
              <div style={{
    display: 'flex',
    flexWrap: 'wrap',
    gap: '8px'
  }}>
                {allWarehouses.map((w, i) => <span key={i} style={{
    background: w.supported ? '#dcfce7' : '#fee2e2',
    color: w.supported ? '#15803d' : '#b91c1c',
    border: `1px solid ${w.supported ? '#bbf7d0' : '#fca5a5'}`,
    borderRadius: '9999px',
    padding: '3px 12px',
    fontSize: '0.85rem',
    fontWeight: '500',
    whiteSpace: 'nowrap'
  }}>
                    {w.name} {w.supported ? '✅' : '❌'}
                  </span>)}
              </div>
            </td>
          </tr>
          <tr>
            <td style={{
    fontWeight: '600',
    paddingRight: '32px',
    paddingBottom: '14px',
    whiteSpace: 'nowrap',
    verticalAlign: 'middle'
  }}>Component Type</td>
            <td style={{
    paddingBottom: '14px',
    verticalAlign: 'middle'
  }}>{componentType}</td>
          </tr>
          <tr>
            <td style={{
    fontWeight: '600',
    paddingRight: '32px',
    paddingBottom: '14px',
    whiteSpace: 'nowrap',
    verticalAlign: 'middle'
  }}>Connection Inputs</td>
            <td style={{
    paddingBottom: '14px',
    verticalAlign: 'middle'
  }}>{connectionInputs}</td>
          </tr>
          <tr>
            <td style={{
    fontWeight: '600',
    paddingRight: '32px',
    whiteSpace: 'nowrap',
    verticalAlign: 'middle'
  }}>Connection Outputs</td>
            <td style={{
    verticalAlign: 'middle'
  }}>{connectionOutputs}</td>
          </tr>
        </tbody>
      </table>
    </div>;
};

<ComponentMetadata warehouses={["Snowflake", "Databricks", "Amazon Redshift", "Google BigQuery"]} componentType="Transformation" connectionInputs="Unlimited" connectionOutputs="Unlimited" />

The **Join** transformation component lets you combine two or more datasets using SQL-style joins.

To configure this component, you choose one of your datasets as a "main" table, then specify any columns that contain common data (such as customer IDs or dates). You can then choose to join all rows, only matching rows, or all rows from one of your datasets.

The [Additional information](#additional-information) section of this guide explains how to configure this component in more detail.

### Use case

The **Join** component can be used to combine data in a number of ways. For example, you can use it to:

* Combine order data and product data by product ID using an inner join to only view data about orders for valid products.
* Combine patient details and health insurance claim data using a left join to see all patient data, even for patients who have not submitted a claim.
* Combine email engagement data and web visits by user ID using a full join to find out how all users are engaging with your content.

***

## Properties

<ResponseField name="Name" type="string" required>
  A human-readable name for the component.
</ResponseField>

{/* <!-- param-start:[mainTable] | warehouses: [snowflake, databricks, redshift, bigquery] --> */}

<ResponseField name="Main table" type="drop-down" required>
  Select one of the tables connected to the component's input.
</ResponseField>

{/* <!-- param-start:[mainTableAlias] | warehouses: [snowflake, databricks, redshift, bigquery] --> */}

<ResponseField name="Main table alias" type="string" required>
  Enter an alias for the main table. You can repeat the actual table name if desired.
</ResponseField>

{/* <!-- param-start:[joins] | warehouses: [snowflake, databricks, redshift, bigquery] --> */}

<ResponseField name="Joins" type="column editor" required>
  * **Join Table:** Select one of the tables connected to the component's input. You can select the main table to perform a self-join.
  * **Join Alias:** Each input table requires a unique alias. You can repeat the actual table name if desired. If you are joining the main table to itself, you need to give it a new alias (i.e. different from the one you assigned as the **Main table alias**).
  * **Join Type:** Specifies how the join is to be performed:
    * **Inner:** Performs an SQL inner join.
    * **Left:** Performs an SQL left join.
    * **Right:** Performs an SQL right join.
    * **Full:** Performs an SQL Full Outer join.

  Click the **Text mode** toggle at the bottom of the dialog to open a multi-line editor that lets you add items in a single block. For more information, read [Text mode](/docs/guides/components-overview#text-mode).

  To use grid variables, toggle **Use Grid Variable** on at the bottom of the dialog. For more information, read [Grid variables](/docs/guides/grid-variables).
</ResponseField>

{/* <!-- param-start:[joinExpressions] | warehouses: [snowflake, databricks, redshift, bigquery] --> */}

<ResponseField name="Join expressions" type="expression editor" required>
  Use the expression editor to create the expressions used to define how the joins will operate. There must be exactly one expression for each join to be performed; you can edit these expressions but not add or delete expressions.

  Each expression must be valid SQL and can use all of the built-in functions supported by your cloud data warehouse.

  * [Snowflake Functions](https://docs.snowflake.net/manuals/sql-reference/functions-all.html)
  * [Databricks functions](https://docs.databricks.com/en/sql/language-manual/sql-ref-functions-builtin.html)
  * [Amazon Redshift Functions](https://docs.aws.amazon.com/redshift/latest/dg/c_SQL_functions.html)
  * [Google BigQuery Functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators)

  There is exactly one expression for each join, and the result of the expression is evaluated as True or False, which indicates whether the two records being compared 'match'. Often this will be a simple 'equality' condition, but it could be more complex, e.g. where a date falls within a start/end date range.

  To use warehouse functions or variables in this field, type the name of the function or variable prefixed by the dollar symbol and surrounded by \{ } brackets, as follows: `${variable}`. Once you type `${`, a drop-down list of autocompleted suggested functions and variables will appear. This list updates as you type; for example, if you type `${DAY`, functions and variables containing `DAY` will be listed. This list also shows the type of each function or variable.
</ResponseField>

{/* <!-- param-start:[columnMappings] | warehouses: [snowflake, databricks, redshift, bigquery] --> */}

<ResponseField name="Column mappings" type="column editor" required>
  * **Source Column:** The name of a source column. There may be duplicate names in input flows, which is why it is important to have unique aliases. The alias name is shown to help distinguish the names.
  * **Target Column:** The target columns must be unique, so you have the opportunity to rename any of the available source columns.

  If you later add another table, the target columns are not re-populated, so you will have to add any additional target columns that have been made available.

  Click the **Add all** button to automatically populate the columns, as follows:

  * The **Source Column** will list all the columns from all the source tables. Each column name is prefixed by the join alias specified in the **Joins** property, followed by a dot.
  * The **Target Column** will list each column name from the **Source Column**, but the dots are replaced with underscores. If the column name is unique across the source tables, there is no prefix.

  You can then selectively edit the list if required, but if you click **Add all** again it will return the list to the default fully populated state, removing any changes you have made.

  Click the **Text mode** toggle at the bottom of the dialog to open a multi-line editor that lets you add items in a single block. For more information, read [Text mode](/docs/guides/components-overview#text-mode).

  To use grid variables, toggle **Use Grid Variable** on at the bottom of the dialog. For more information, read [Grid variables](/docs/guides/grid-variables).
</ResponseField>

## Additional information

In SQL terms, the Join component generates a SELECT query with multiple inputs in the FROM clause, joined with an ON clause generated from the join expressions you configure in the component. For more details on the use of table joins, refer to any good SQL documentation.

The Join component must have at least two inputs, which provide the two tables that will be joined together. Typically, you will use two [Table Input](/docs/components/table-input) components, each of which is configured to point to one table. You can add a third, fourth, or even more inputs, allowing you to perform complex joins on multiple tables within the same Join component.

Note that it is possible to join a table to *itself*, in which case you need two (or more) Table Input components that each point to the same table.

When configuring the Join component, you will specify which of the inputs is the **Main table**. The main table is considered the *left* table in terms of SQL joins, and must contain the *foreign key*, which will be used to join the other input tables to it. The main table must be given an **Alias**, which is used to refer to the table in join expressions. The reason for this is to allow the table to be joined to itself, as it can be referred to by two different aliases in join expressions to avoid confusion or conflict between which is the *left* and which is the *right* table.

Multiple joins can be created in a single Join component. When the component runs, the joins are executed in the order they have been listed in the **Joins** property, top to bottom, starting with the main table.

Joins are created using an expression editor, which assists you in writing the expressions used to define how the joins will operate. There must be exactly one expression for each join to be performed; you can edit these expressions but not add or delete expressions. Each expression should be a boolean expression which determines which rows in the second (*right*) table match those in the main (*left*) table for the purposes of this join. An example is:

```
"flights"."tailnum" = "planes"."tailnum"
```

This expression tells the component to select all rows from the input table "planes" where the "tailnum" field matches the "tailnum" field in the input table "flights". The join connects rows ONLY when the join condition is met (i.e. the join expression resolves to "true"). Note that "flights" and "planes" are aliases we have specified for the main table and the second table respectively; we never use actual table names in join expressions.

In the expression editor, creating this expression is as simple as selecting the first field from the **Fields** list in the editor, selecting the **=** operator, and then selecting the second field from the list.

Expressions can be more complex than the simple equality illustrated above. For example, an expression can resolve whether a date falls within a particular start/end range.

Each expression must be valid SQL, and can use all the built-in functions of your cloud data warehouse. For descriptions of built-in cloud data warehouse functions, see:

* [Snowflake](https://docs.snowflake.net/manuals/sql-reference/functions-all.html)
* [Redshift](http://docs.aws.amazon.com/redshift/latest/dg/c_SQL_functions.html)
* [Databricks](https://docs.databricks.com/sql/language-manual/sql-ref-functions-builtin.html)
* [Google BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators)

The Join component can perform the following types of standard SQL join:

| Join type | Description                                                                                                                                                    | When to use it                                                                  |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Inner     | Performs an SQL inner join. Returns only the rows where there's a match in both tables.                                                                        | You want data that exists in both tables.                                       |
| Left      | Performs an SQL left join. Returns all rows from the left table, and matched rows from the right. If there's no match, it returns NULL for the right table.    | You want everything from the left, whether or not there's a match on the right. |
| Right     | Performs an SQL right join. Returns all rows from the right table, and matched rows from the left. If there's no match, it returns NULL for the left table.    | You want everything from the right, regardless of matches on the left.          |
| Full      | Performs an SQL full outer join. Returns all rows from both tables. If a row doesn't have a match in the other table, the missing values are filled with NULL. | You want everything from both tables, matched or not.                           |

You can also perform a **cartesian join**, which returns all the rows in all the tables, by creating an inner join with an expression that will always evaluate to "true" (for example, `1 = 1`).

For more information, read [Join (SQL)](https://en.wikipedia.org/wiki/Join_\(SQL\)).
