# JDBC API

dbt platform | Starter, Enterprise, Enterprise+

The Semantic Layer Java Database Connectivity (JDBC) API enables users to query metrics and dimensions using the JDBC protocol, while also providing standard metadata functionality.

A JDBC driver is a software component enabling a Java application to interact with a data platform. Here's some more information about our JDBC API:

* The Semantic Layer JDBC API utilizes the open-source JDBC driver with ArrowFlight SQL protocol.
* You can download the JDBC driver from [Maven](https://search.maven.org/remotecontent?filepath=org/apache/arrow/flight-sql-jdbc-driver/12.0.0/flight-sql-jdbc-driver-12.0.0.jar).
* The Semantic Layer supports ArrowFlight SQL driver version 12.0.0 and higher.
* You can embed the driver into your application stack as needed, and you can use dbt Labs' [example project](https://github.com/dbt-labs/example-semantic-layer-clients) for reference.
* If you’re a partner or user building a homegrown application, you’ll need to install an AWS root CA to the Java Trust [documentation](https://www.amazontrust.com/repository/) (specific to Java and JDBC call).

dbt Labs partners can use the JDBC API to build integrations in their tools with the Semantic Layer

## Using the JDBC API

If you are a dbt user or partner with access to dbt and the [Semantic Layer](../use-dbt-semantic-layer/dbt-sl.md), you can [setup](../use-dbt-semantic-layer/setup-sl.md) and test this API with data from your own instance by configuring the Semantic Layer and obtaining the right JDBC connection parameters described in this document.

You *may* be able to use our JDBC API with tools that do not have an official integration with the Semantic Layer. If the tool you use allows you to write SQL and either supports a generic JDBC driver option (such as DataGrip) or supports Dremio and uses ArrowFlightSQL driver version 12.0.0 or higher, you can access the Semantic Layer API.

Refer to [Get started with the Semantic Layer](../../guides/sl-snowflake-qs.md) for more info.

Note that the Semantic Layer GraphQL API doesn't support `ref` to call dbt objects. Instead, use the complete qualified table name. If you're using dbt macros at query time to calculate your metrics, you should move those calculations into your Semantic Layer metric definitions as code.

## Authentication

dbt authorizes requests to the Semantic Layer API. You need to provide an Environment ID, Host, and [service account tokens](./service-tokens.md) or [personal access tokens](./user-tokens.md).

## Connection parameters

The JDBC connection requires a few different connection parameters.

This is an example of a URL connection string and the individual components:

```text
jdbc:arrow-flight-sql://semantic-layer.cloud.getdbt.com:443?&environmentId=202339&token=AUTHENTICATION_TOKEN
```

| JDBC parameter                    | Description                                                                                                                                                                                                                                                                                                                   | Example                                                                                       |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `jdbc:arrow-flight-sql://`        | The protocol for the JDBC driver.                                                                                                                                                                                                                                                                                             | `jdbc:arrow-flight-sql://`                                                                    |
| `semantic-layer.cloud.getdbt.com` | The [access URL](../platform/about-platform/access-regions-ip-addresses.md) for your account's dbt region. You must always add the `semantic-layer` prefix before the access URL.                                                                                                                   | For dbt deployment hosted in North America, use `semantic-layer.cloud.getdbt.com`             |
| `environmentId`                   | The unique identifier for the dbt production environment, you can retrieve this from the dbt URL<br />when you navigate to **Environments** under **Deploy**.                                                                                                                                                                 | If your URL ends with `.../environments/222222`, your `environmentId` is `222222`<br /><br /> |
| `AUTHENTICATION_TOKEN`            | You can use either a dbt [service token](./service-tokens.md) with “Semantic Layer Only” and "Metadata Only" permissions or a dbt [personal access token](./user-tokens.md). Create a new service or personal token on the **Account Settings** page. | `token=AUTHENTICATION_TOKEN`                                                                  |

\*Note — If you're testing locally on a tool like DataGrip, you may also have to provide the following variable at the end or beginning of the JDBC URL `&disableCertificateVerification=true`.

## Querying the API for metadata

The Semantic Layer JDBC API has built-in metadata calls which can provide a user with information about their metrics and dimensions.

Expand the following toggles for examples and metadata commands:

 Fetch defined metrics

You can use this query to fetch all defined metrics in your dbt project:

```bash
select * from {{ 
	semantic_layer.metrics() 
}}
```

 Fetch dimension for a metric

You can use this query to fetch all dimensions for a metric.

Note, metrics is a required argument that lists one or multiple metrics in it.

```bash
select * from {{ 
    semantic_layer.dimensions(metrics=['food_order_amount'])}}
```

 Fetch granularities for metrics

You can use this query to fetch queryable granularities for a list of metrics.

This API request allows you to only show the time granularities that make sense for the primary time dimension of the metrics (such as metric\_time), but if you want queryable granularities for other time dimensions, you can use the dimensions() call, and find the column queryable\_granularities.

Note, metrics is a required argument that lists one or multiple metrics.

```bash
select * from {{
    semantic_layer.queryable_granularities(metrics=['food_order_amount', 'order_gross_profit'])}}
```

 Fetch available metrics given dimensions

You can use this query to fetch available metrics given dimensions. This command is essentially the opposite of getting dimensions given a list of metrics.

Note, group\_by is a required argument that lists one or multiple dimensions.

```bash
select * from {{
    semantic_layer.metrics_for_dimensions(group_by=['customer__customer_type'])
}}
```

 Fetch granularities for all time dimensions

You can use this example query to fetch available granularities for all time dimensions (the similar queryable granularities API call only returns granularities for the primary time dimensions for metrics).

The following call is a derivative of the dimensions() call and specifically selects the granularity field.

```bash
select NAME, QUERYABLE_GRANULARITIES from {{
    semantic_layer.dimensions(
        metrics=["order_total"]
    )
}}
```

 Paginate metadata calls

In the case when you don't want to return the full result set from a metadata call, you can paginate the results for both `semantic_layer.metrics()` and `semantic_layer.dimensions()` calls using the `page_size` and `page_number` parameters.

* `page_size`: This is an optional variable which sets the number of records per page. If left as None, there is no page limit.
* `page_number`: This is an optional variable which specifies the page number to retrieve. Defaults to `1` (first page) if not specified.

Examples:

```sql
-- Retrieves the 5th page with a page size of 10 metrics
select * from {{ semantic_layer.metrics(page_size=10, page_number=5) }}

-- Retrieves the 1st page with a page size of 10 metrics
select * from {{ semantic_layer.metrics(page_size=10) }}

-- Retrieves all metrics without pagination
select * from {{ semantic_layer.metrics() }}
```

You can use the same pagination parameters for `semantic_layer.dimensions(...)`.

 List saved queries

You can use this example query to list all available saved queries in your dbt project.

**Command**

```bash
select * from semantic_layer.saved_queries()
```

**Output**

```bash
| NAME | DESCRIPTION | LABEL | METRICS | GROUP_BY | WHERE_FILTER |
```

 Fetch metric aliases

You can query metrics using aliases for simpler or more intuitive names, even if the alias isn't defined in the metric configuration. The query returns the alias as the metric name, for example:

```sql
select * from {{
    semantic_layer.query(metrics=[Metric("metric_name", alias="metric_alias")])
}}
```

In this example, if you define an alias for `revenue` as `banana`, the query will return a column named `banana` even if `banana` isn't defined in the metric configuration. However, when using `where` Jinja clauses, you need to reference the *actual* metric name (`revenue` in this case) instead of the alias.

For more a more detailed example, see [Query metric alias](#query-metric-alias).

## Querying the API for values

To query values, the following parameters are available. Your query must have *either* a `metric` **or** a `group_by` parameter to be valid.

| Parameter     | Description                                                                                                                                                                                                                                  | Example                                                                                                                                                                     |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `metrics`     | The metric name as defined in your dbt metric configuration                                                                                                                                                                                  | `metrics=['revenue']`                                                                                                                                                       |
| `group_by`    | Dimension names or entities to group by. We require a reference to the entity of the dimension (other than for the primary time dimension), which is pre-appended to the front of the dimension name with a double underscore.               | `group_by=['user__country', 'metric_time']`                                                                                                                                 |
| `grain`       | A parameter specific to any time dimension and changes the grain of the data from the default for the metric.                                                                                                                                | `group_by=[Dimension('metric_time')`<br />`grain('week\|day\|month\|quarter\|year')]`                                                                                       |
| `where`       | A where clause that allows you to filter on dimensions and entities using parameters. This takes a filter list OR string. Inputs come with `Dimension`, and `Entity` objects. Granularity is required if the `Dimension` is a time dimension | `"{{ where=Dimension('customer__country') }} = 'US')"`                                                                                                                      |
| `limit`       | Limit the data returned                                                                                                                                                                                                                      | `limit=10`                                                                                                                                                                  |
| `order`       | Order the data returned by a particular field                                                                                                                                                                                                | `order_by=['order_gross_profit']`, use `-` for descending, or full object notation if the object is operated on: `order_by=[Metric('order_gross_profit').descending(True)`] |
| `compile`     | If true, returns generated SQL for the data platform but does not execute                                                                                                                                                                    | `compile=True`                                                                                                                                                              |
| `saved_query` | A saved query you can use for frequently used queries.                                                                                                                                                                                       | `select * from {{ semantic_layer.query(saved_query="new_customer_orders"`                                                                                                   |

### Note on time dimensions and `metric_time`

You will notice that in the list of dimensions for all metrics, there is a dimension called `metric_time`. `Metric_time` is a reserved keyword for any metric's default aggregation time dimension. For any time-series metric, the `metric_time` keyword should always be available for use in queries. This is a common dimension across *all* metrics in a semantic graph.

You can look at a single metric or hundreds of metrics, and if you group by `metric_time`, it will always give you the correct time series.

Additionally, when performing granularity calculations that are global (not specific to a particular time dimension), we recommend you always operate on `metric_time` and you will get the correct answer.

Note that `metric_time` should be available in addition to any other time dimensions that are available for the metric(s). In the case where you are looking at one metric (or multiple metrics from the same data source), the values in the series for the primary time dimension and `metric_time` are equivalent.

## Examples

The following sections provide examples of how to query metrics using the JDBC API:

* [Fetch metadata for metrics](#fetch-metadata-for-metrics) — Filter/add any SQL outside of the templating syntax.
* [Query common dimensions](#query-common-dimensions) — Select common dimensions for multiple metrics.
* [Query grouped by time](#query-grouped-by-time) — Fetch revenue and new customers grouped by time.
* [Query with a time grain](#query-with-a-time-grain) — Fetch multiple metrics with a change in time dimension granularities.
* [Group by categorical dimension](#group-by-categorical-dimension) — Group by a categorical dimension.
* [Query only a dimension](#query-only-a-dimension) — Get the full list of dimension values for the chosen dimension.
* [Query by all dimensions](#query-by-all-dimensions) — Query by all valid dimensions.
* [Query with where filters](#query-with-where-filters) — Use the `where` parameter to filter on dimensions and entities using parameters.
* [Query with a limit](#query-with-a-limit) — Query using a `limit` or `order_by` clause.
* [Query with order by examples](#query-with-order-by-examples) — Query with `order_by`, accepts basic string that's a Dimension, Metric, or Entity. Defaults to ascending order. Add a `-` sign in front of the object for descending order.
* [Query with compile keyword](#query-with-compile-keyword) — Query using a compile keyword to preview the final SQL before execution.
* [Query a saved query](#query-a-saved-query) — Query using a saved query with optional parameters like `limit` or `where`.
* [Query metric alias](#query-metric-alias) — Query metrics using aliases, which allow you to use simpler or more intuitive names for metrics instead of their full definitions.
* [Multi-hop joins](#multi-hop-joins) — Query across multiple related tables (multi-hop joins) using the `entity_path` argument to specify the path between related entities.

### Fetch metadata for metrics

You can filter/add any SQL outside of the templating syntax. For example, you can use the following query to fetch the name and dimensions for a metric:

```bash
select name, dimensions from {{ 
	semantic_layer.metrics() 
	}}
	WHERE name='food_order_amount'
```

### Query common dimensions

You can select common dimensions for multiple metrics. Use the following query to fetch the name and dimensions for multiple metrics:

```bash
select * from {{ 
	semantic_layer.dimensions(metrics=['food_order_amount', 'order_gross_profit'])
	}}
```

### Query grouped by time

The following example query uses the [shorthand method](#faqs) to fetch revenue and new customers grouped by time:

```bash
select * from {{
	semantic_layer.query(metrics=['food_order_amount','order_gross_profit'], 
	group_by=['metric_time'])
	}}
```

### Query with a time grain

Use the following example query to fetch multiple metrics with a change in time dimension granularities:

```bash
select * from {{
	semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'], 
	group_by=[Dimension('metric_time').grain('month')])
	}}
```

### Group by categorical dimension

Use the following query to group by a categorical dimension:

```bash
select * from {{
	semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'], 
	group_by=[Dimension('metric_time').grain('month'), 'customer__customer_type'])
	}}
```

### Query only a dimension

In this case, you'll get the full list of dimension values for the chosen dimension.

```bash
select * from {{
    semantic_layer.query(group_by=['customer__customer_type'])
                  }}
```

### Query by all dimensions

You can use the `semantic_layer.query_with_all_group_bys` endpoint to query by all valid dimensions.

```sql
select * from {{
    semantic_layer.query_with_all_group_bys(metrics =['revenue','orders','food_orders'],
    compile= True)
}}
```

This returns all dimensions that are valid for the set of metrics in the request.

### Query with where filters

Where filters in API allow for a filter list or string. We recommend using the filter list for production applications as this format will realize all benefits from the Predicate pushdown where possible.

Where Filters have a few objects that you can use:

* `Dimension()` — Used for any categorical or time dimensions. `Dimension('metric_time').grain('week')` or `Dimension('customer__country')`.

* `TimeDimension()` — Used as a more explicit definition for time dimensions, optionally takes in a granularity `TimeDimension('metric_time', 'month')`.

* `Entity()` — Used for entities like primary and foreign keys - `Entity('order_id')`.

You can use the following example to query using a `where` filter with the string format:

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
group_by=[Dimension('metric_time').grain('month'),'customer__customer_type'],
where="{{ Dimension('metric_time').grain('month')  }} >= '2017-03-09' AND {{ Dimension('customer__customer_type' }} in ('new') AND {{ Entity('order_id') }} = 10")
}}
```

* (Recommended for better performance) Use the following example to query using a `where` filter with a filter list format:

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
group_by=[Dimension('metric_time').grain('month'),'customer__customer_type'],
where=["{{ Dimension('metric_time').grain('month') }} >= '2017-03-09'", "{{ Dimension('customer__customer_type') }} in ('new')", "{{ Entity('order_id') }} = 10"])
}}
```

### Query with a limit

Use the following example to query using a `limit` or `order_by` clause:

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
  group_by=[Dimension('metric_time')],
  limit=10)
  }}
```

### Query with order by examples

Order By can take a basic string that's a Dimension, Metric, or Entity, and this will default to ascending order

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
  group_by=[Dimension('metric_time')],
  limit=10,
  order_by=['order_gross_profit'])
  }}
```

For descending order, you can add a `-` sign in front of the object. However, you can only use this short-hand notation if you aren't operating on the object or using the full object notation.

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
  group_by=[Dimension('metric_time')],
  limit=10,
  order_by=['-order_gross_profit'])
  }}
```

If you are ordering by an object that's been operated on (for example, you changed the granularity of the time dimension), or you are using the full object notation, descending order must look like:

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
  group_by=[Dimension('metric_time').grain('week')],
  limit=10,
  order_by=[Metric('order_gross_profit').descending(True), Dimension('metric_time').grain('week').descending(True) ])
  }}
```

Similarly, this will yield ascending order:

```bash
select * from {{
semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
  group_by=[Dimension('metric_time').grain('week')],
  limit=10,
  order_by=[Metric('order_gross_profit'), Dimension('metric_time').grain('week')])
  }}
```

### Query with compile keyword

* Use the following example to query using a `compile` keyword:

  ```sql
  select * from {{
  semantic_layer.query(metrics=['food_order_amount', 'order_gross_profit'],
      group_by=[Dimension('metric_time').grain('month'),'customer__customer_type'],
      compile=True)
      }}
  ```

* Use the following example to compile SQL with a [saved query](../build/saved-queries.md). You can use this for frequently used queries.

  ```sql
  select * from {{ semantic_layer.query(saved_query="new_customer_orders", limit=5, compile=True}}
  ```

A note on querying saved queries

When querying [saved queries](../build/saved-queries.md),you can use parameters such as `where`, `limit`, `order`, `compile`, and so on. However, keep in mind that you can't access `metric` or `group_by` parameters in this context. This is because they are predetermined and fixed parameters for saved queries, and you can't change them at query time. If you would like to query more metrics or dimensions, you can build the query using the standard format.

### Query a saved query

Use the following example to query a [saved query](../build/saved-queries.md):

```sql
select * from {{ semantic_layer.query(saved_query="new_customer_orders", limit=5}}
```

The JDBC API will use the saved query (`new_customer_orders`) as defined and apply a limit of 5 records.

### Query metric alias

You can query metrics using aliases, which allow you to use simpler or more intuitive names for metrics instead of their full definitions.

```sql
select * from {{
    semantic_layer.query(metrics=[Metric("revenue", alias="metric_alias")])
}}
```

For example, let's say your metric configuration includes an alias like `total_revenue_global` for the `order_total` metric. You can query the metric using the alias instead of the original name:

```sql
select * from {{
    semantic_layer.query(metrics=[Metric("order_total", alias="total_revenue_global")], group_by=['metric_time'])
}}
```

The result will be:

```text
| METRIC_TIME   | TOTAL_REVENUE_GLOBAL |
|:-------------:|:------------------:  |
| 2023-12-01    |              1500.75 |
| 2023-12-02    |              1725.50 |
| 2023-12-03    |              1850.00 |
```

tip

Note that you need to use the actual metric name when using the `where` Jinja clauses. For example, if you used `banana` as an alias for `revenue`, you need to use the actual metric name, `revenue`, in the `where` clause, not `banana`.

```graphql
semantic_layer.query(metrics=[Metric("revenue", alias="banana")], where="{{ Metric('revenue') }} > 0")
```

### Multi-hop joins

In cases where you need to query across multiple related tables (multi-hop joins), use the `entity_path` argument to specify the path between related entities. The following are examples of how you can define these joins:

* In this example, you're querying the `location_name` dimension but specifying that it should be joined using the `order_id` field.

  ```sql
  {{Dimension('location__location_name', entity_path=['order_id'])}}
  ```

* In this example, the `salesforce_account_owner` dimension is joined to the `region` field, with the path going through `salesforce_account`.

  ```sql
  {{ Dimension('salesforce_account_owner__region',['salesforce_account']) }}
  ```

## FAQs

I'm receiving an \`Failed ALPN\` error when trying to connect to the dbt Semantic Layer.

If you're receiving a `Failed ALPN` error when trying to connect the dbt Semantic Layer with the various [data integration tools](../platform-integrations/avail-sl-integrations.md) (such as Tableau, DBeaver, Datagrip, ADBC, or JDBC), it typically happens when connecting from a computer behind a corporate VPN or Proxy (like Zscaler or Check Point).

The root cause is typically the proxy interfering with the TLS handshake as the Semantic Layer uses gRPC/HTTP2 for connectivity. To resolve this:

* If your proxy supports gRPC/HTTP2 but isn't configured to allow ALPN, adjust its settings accordingly to allow ALPN. Or create an exception for the dbt domain.
* If your proxy does not support gRPC/HTTP2, add an SSL interception exception for the dbt domain in your proxy settings

This should help in successfully establishing the connection without the Failed ALPN error.

 Why do some dimensions use different syntax, like \`metric\_time\` versus \`Dimension('metric\_time')\`?

When you select a dimension on its own, such as `metric_time` you can use the shorthand method which doesn't need the “Dimension” syntax.

However, when you perform operations on the dimension, such as adding granularity, the object syntax `[Dimension('metric_time')` is required.

 What does the double underscore \`'\_\_'\` syntax in dimensions mean?

The double underscore `"__"` syntax indicates a mapping from an entity to a dimension, as well as where the dimension is located. For example, `user__country` means someone is looking at the `country` dimension from the `user` table.

 What is the default output when adding granularity?

The default output follows the format `{{time_dimension_name}__{granularity_level}}`.

So for example, if the `time_dimension_name` is `ds` and the granularity level is yearly, the output is `ds__year`.

## Related docs

* [Semantic Layer integration best practices](../../guides/sl-partner-integration-guide.md)

## Was this page helpful?

YesNo

[Privacy policy](https://www.getdbt.com/cloud/privacy-policy)[Create a GitHub issue](https://github.com/dbt-labs/docs.getdbt.com/issues)

This site is protected by reCAPTCHA and the Google [Privacy Policy](https://policies.google.com/privacy) and [Terms of Service](https://policies.google.com/terms) apply.
