sql_on

Usage

explore: view_name_1 {
  join: view_name_2 {
    sql_on: ${view_name_1.id} = ${view_name_2.id} ;;
  }
}
Hierarchy
sql_on
Default Value
None

Accepts
A SQL ON clause

Special Rules
sql_on, sql_foreign_key, and foreign_key may not be used at the same time within the same join

Definition

sql_on establishes a join relationship between a view and its Explore, based on a SQL ON clause that you provide.

For LookML, the order of conditions in sql_on does not matter. So sql_on: ${order.user_id} = ${user.id} ;; and sql_on: ${user.id} = ${order.user_id} ;; are equivalent. You can put the conditions in either order, unless the order is relevant to your database's SQL dialect.

A view can be joined directly to an Explore when using sql_on, or it can be joined through a second view that is already joined to that Explore.

An example of the first case, where a view is joined directly to the Explore, looks like this:

explore: order {
  join: customer {
    sql_on: ${order.customer_id} = ${customer.id} ;;
  }
}

The SQL that Looker would generate from this LookML is:

SELECT    ...
FROM      order
LEFT JOIN customer
ON        order.customer_id = customer.id

In the second case, a view is joined to an Explore through an intermediate view that is already joined to that Explore. An example of that would be:

explore: order_items {
  join: order {
    sql_on: ${order_items.order_id} = ${order.id} ;;
  }
  join: customer {
    sql_on: ${order.customer_id} = ${customer.id} ;;
  }
}

Here customer cannot be joined directly to order_items. Instead it must be joined through order. The SQL that Looker would generate from this LookML is:

SELECT    ...
FROM      order_items
LEFT JOIN order
ON        order_items.order_id = order.id
LEFT JOIN customer
ON        order.customer_id = customer.id

To make this work properly, you can see that we simply need to use the correct view names in our field references. Since customer needs to join to a field in order, we reference ${order.customer_id}.

In some older models, you might see fields referenced with the view_name.native_column_name syntax. While this still works, using the ${view_name.looker_dimension_name} syntax instead has an important advantage: you can avoid the need for the required_joins parameter. This concept is explained in more detail in the Use required_joins when ${view_name.looker_dimension_name} syntax can't be used section on this page.

Conditional joins

It's also possible to allow user input to be used in sql_on. Although there are various reasons you may want to do this, optimizing query speed on MPP databases (such as Redshift) is a major use case, as described in the Conditions in Join Clauses Community post.

To add user input to your join condition, you'll first need to create a filter for their input. These types of filters are described in more detail on our Templated Filters page. Their basic form is:

view: view_name {
  filter: filter_name {
    type: number | datetime | date | string
  }
}

Once you've added a filter to collect the user input, you use it in your sql_on parameter like this:

{% condition view_name.filter_name %} view_name.dimension_name {% endcondition %}

For example:

explore: order {
  join: customer {
    sql_on:
      ${order.customer_id} = ${customer.id} AND
      {% condition customer.creation_date_filter %} customer.created_at {% endcondition %} ;;
  }
}

This would be interpreted to mean: set customer.created_at equal to the value from customer.creation_date_filter.

Using _in_query, _is_selected, and _is_filtered Liquid variables

The _in_query, _is_selected, and _is_filtered Liquid variables can be useful when used with sql_on parameter. They can allow you to modify join relationships based on the fields that a user has selected for their query. For example:

explore: dates {
  join: dynamic_order_counts {
    sql_on:
      ${dynamic_order_counts.period} =
      {% if dates.reporting_date._in_query %}
        ${dates.date_string}
      {% elsif dates.reporting_week._in_query %}
        ${dates.week_string}
      {% else %}
        ${dates.month_string}
      {% endif %} ;;
  }
}

Examples

Join the view named customer to the Explore named order by matching up the customer_id dimension from order with the id dimension from customer:

explore: order {
  join: customer {
    sql_on: ${order.customer_id} = ${customer.id} ;;
  }
}

Join the view named customer to the Explore named order_items through the view called order. Match up the customer_id dimension from order with the id dimension from customer. Match up the order_id dimension from order_items with the id dimension from order. This would be specified as follows:

explore: order_items {
  join: order {
    sql_on: ${order_items.order_id} = ${order.id} ;;
  }
  join: customer {
    sql_on: ${order.customer_id} = ${customer.id} ;;
  }
}

Join the views named order and inventory_items to the Explore named order_items. Match up the inventory_id dimension from order_items with the id dimension from inventory_item. Match up the order_id dimension from order_items with the id dimension from order. This would be specified as follows:

explore: order_items {
  join: order {
    sql_on: ${order_items.order_id} = ${order.id} ;;
  }
  join: inventory_item {
    sql_on: ${order_items.inventory_id} = ${inventory_item.id} ;;
  }
}

Things to know

Use required_joins when ${view_name.looker_dimension_name} syntax can't be used

When you reference fields in sql_on using the ${view_name.looker_dimension_name} syntax, you do not need to worry about using required_joins.

However, some older models still use the view_name.native_column_name syntax. There are also some cases when you cannot use the ${view_name.looker_dimension_name} syntax, such as when you want to apply custom SQL.

In these situations, you may need to use required_joins. They are discussed in more detail on the required_joins parameter documentation page.