# GraphQL

Платформа dbt | Starter, Enterprise, Enterprise+

[GraphQL](https://graphql.org/) (GQL) — это open-source язык запросов для API. Он предлагает более эффективный и гибкий подход по сравнению с традиционными RESTful API.

С помощью GraphQL пользователи могут запрашивать конкретные данные с помощью одного запроса, что уменьшает необходимость в многочисленных обращениях к серверу. Это улучшает производительность и минимизирует сетевые издержки.

GraphQL имеет несколько преимуществ, таких как самодокументируемость, наличие системы строгой типизации, поддержка версионирования и эволюции, возможность быстрого развития и наличие мощной экосистемы. Эти функции делают GraphQL мощным выбором для API, которые придают приоритет гибкости, производительности и продуктивности разработчиков.

## GraphQL API семантического слоя dbt

GraphQL API Semantic Layer позволяет исследовать и запрашивать метрики и измерения. Благодаря своей самодокументируемости вы можете удобно изучать доступные вызовы с помощью обозревателя схемы.

URL-адреса обозревателя схем различаются в зависимости от вашего [региона развертывания](https://docs.getdbt.tech/docs/platform/about-platform/access-regions-ip-addresses.md). Используйте следующую таблицу, чтобы найти нужную ссылку для вашего региона:

| Тип развертывания                 | URL проводника схемы                                                                                                                                                                                                   |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Северная Америка, мультиарендатор | <https://semantic-layer.cloud.getdbt.com/api/graphql>                                                                                                                                                                  |
| EMEA, мультиарендатор             | <https://semantic-layer.emea.dbt.com/api/graphql>                                                                                                                                                                      |
| APAC, мультиарендатор             | <https://semantic-layer.au.dbt.com/api/graphql>                                                                                                                                                                        |
| Одноарендатор                     | `https://semantic-layer.YOUR_ACCESS_URL/api/graphql`<br /><br />Замените `YOUR_ACCESS_URL` на ваш конкретный префикс аккаунта, за которым следует соответствующий URL доступа для вашего региона и плана.              |
| Мультиячейка                      | `https://YOUR_ACCOUNT_PREFIX.semantic-layer.REGION.dbt.com/api/graphql`<br /><br />Замените `YOUR_ACCOUNT_PREFIX` на ваш конкретный идентификатор аккаунта и `REGION` на ваше местоположение, например, `us1.dbt.com`. |

**Пример**

* Если ваш URL доступа для одноарендатора `ABC123.getdbt.com`, ваш URL проводника схемы будет `https://semantic-layer.ABC123.getdbt.com/api/graphql`.

Партнёры dbt могут использовать GraphQL API Semantic Layer для создания интеграций с Semantic Layer.

Обратите внимание, что GraphQL API Semantic Layer не поддерживает `ref` для обращения к объектам dbt. Вместо этого необходимо использовать полностью квалифицированное имя таблицы. Если вы используете макросы dbt во время выполнения запроса для расчёта метрик, эти вычисления следует перенести в определения метрик Semantic Layer в виде кода.

## Требования для использования GraphQL API

* Проект dbt на dbt v1.6 или выше
* Метрики определены и настроены
* [Сервисный токен](https://docs.getdbt.tech/docs/dbt-apis/service-tokens.md) dbt с разрешениями "Semantic Layer Only" и "Metadata Only" или [персональный токен доступа](https://docs.getdbt.tech/docs/dbt-apis/user-tokens.md)

Если вы пользователь dbt или партнер с доступом к dbt Cloud и [семантическому слою dbt](https://docs.getdbt.tech/docs/use-dbt-semantic-layer/dbt-sl.md), вы можете [настроить](https://docs.getdbt.tech/docs/use-dbt-semantic-layer/setup-sl.md) и протестировать этот API с данными из вашей собственной инстанции, настроив семантический слой и получив правильные параметры подключения GQL, описанные в этом документе.

Если вы пользователь или партнёр dbt с доступом к dbt и [Semantic Layer](https://docs.getdbt.tech/docs/use-dbt-semantic-layer/dbt-sl.md), вы можете [настроить](https://docs.getdbt.tech/docs/use-dbt-semantic-layer/setup-sl.md) и протестировать этот API на данных из собственного экземпляра, настроив Semantic Layer и получив необходимые параметры подключения GQL, описанные в этом документе.

Подробнее см. в руководстве [Начало работы с Semantic Layer](https://docs.getdbt.tech/guides/sl-qs.md).

Для аутентификации используется либо [токен служебной учётной записи](https://docs.getdbt.tech/docs/dbt-apis/service-tokens.md) dbt, либо [персональный токен доступа](https://docs.getdbt.tech/docs/dbt-apis/user-tokens.md), который передаётся в заголовке следующим образом. Чтобы изучить схему, вы можете ввести эти данные в разделе «header».

```shell
{"Authorization": "Bearer <AUTHENTICATION TOKEN>"}
```

Для каждого GQL-запроса также требуется `environmentId` среды dbt. Для аутентификации API использует как служебный или персональный токен в заголовке, так и `environmentId`.

### Вызовы метаданных

#### Получение диалекта платформы данных

В некоторых случаях в вашем приложении может быть полезно знать диалект или платформу данных, которая используется внутри соединения Semantic Layer (например, если вы формируете фильтры `where` на основе пользовательского интерфейса, а не SQL, введённого пользователем).

GraphQL API имеет простой способ получить это с помощью следующего запроса:

```graphql
{
  environmentInfo(environmentId: BigInt!) {
    dialect
  }
}
```

#### Получение доступных метрик

```graphql
metricsPaginated(
  environmentId: BigInt!
  search: String = null
  groupBy: [GroupByInput!] = null
  pageNum: Int! = 1
  pageSize: Int = null
): MetricResultPage! {
  items: [Metric!]!
  pageNum: Int!
  pageSize: Int
  totalItems: Int!
  totalPages: Int!
}
```

#### Получение доступных измерений для метрик

```graphql
dimensionsPaginated(
    environmentId: BigInt!
    metrics: [MetricInput!]!
    search: String = null
    pageNum: Int! = 1
    pageSize: Int = null
): DimensionResultPage! {
    items: [Dimension!]!
    pageNum: Int!
    pageSize: Int
    totalItems: Int!
    totalPages: Int!
}
```

#### Получение доступных гранулярностей для заданных метрик

Примечание: Этот вызов для `queryableGranularities` возвращает только запрашиваемые гранулярности для времени метрики - основного временного измерения для всех выбранных метрик.

```graphql
queryableGranularities(
  environmentId: BigInt!
  metrics: [MetricInput!]!
): [TimeGranularity!]!
```

Вы также можете получить запрашиваемые гранулярности для всех других измерений, используя вызов `dimensions`:

```graphql
{
  dimensionsPaginated(environmentId: BigInt!, metrics:[{name:"order_total"}]) {
    items {
      name
      queryableGranularities # --> ["DAY", "WEEK", "MONTH", "QUARTER", "YEAR"]
    }
  }
}
```

Вы также можете опционально получить доступ к этому из конечной точки метрик:

```graphql
{
  metricsPaginated(environmentId: BigInt!) {
    items {
      name
      dimensions {
        name
        queryableGranularities
      }
    }
  }
}
```

#### Получение сущностей

```graphql
entitiesPaginated(
    environmentId: BigInt!
    metrics: [MetricInput!] = null
    search: String = null
    pageNum: Int! = 1
    pageSize: Int = null
): EntityResultPage! {
    items: [Entity!]!
    pageNum: Int!
    pageSize: Int
    totalItems: Int!
    totalPages: Int!
}
```

#### Получение сущностей и измерений для группировки метрик

```graphql
groupBysPaginated(
    environmentId: BigInt!
    metrics: [MetricInput!] = null
    search: String = null
    pageNum: Int! = 1
    pageSize: Int = null
): EntityDimensionResultPage! {
    items: [EntityDimension!]!
    pageNum: Int!
    pageSize: Int
    totalItems: Int!
    totalPages: Int!
}
```

#### Типы метрик

```graphql
Metric {
  name: String!
  description: String
  type: MetricType!
  typeParams: MetricTypeParams!
  filter: WhereFilter
  dimensions: [Dimension!]!
  queryableGranularities: [TimeGranularity!]!
}
```

```text
MetricType = [SIMPLE, RATIO, CUMULATIVE, DERIVED]
```

#### Параметры типа метрики

(Версии dbt: v1.12 и новее)

```graphql
MetricTypeParams {
  numerator: MetricInput
  denominator: MetricInput
  expr: String
  window: MetricTimeWindow
  grainToDate: TimeGranularity
  metrics: [MetricInput!]
}
```

#### Типы измерений

```graphql
Dimension {
  name: String!
  description: String
  type: DimensionType!
  typeParams: DimensionTypeParams
  isPartition: Boolean!
  expr: String
  queryableGranularities: [TimeGranularity!]!
}
```

```text
DimensionType = [CATEGORICAL, TIME]
```

#### Список сохранённых запросов

Получить список всех сохранённых запросов для указанного окружения:

```graphql
savedQueriesPaginated(
    environmentId: BigInt!
    search: String = null
    pageNum: Int! = 1
    pageSize: Int = null
): SavedQueryResultPage! {
    items: [SavedQuery!]!
    pageNum: Int!
    pageSize: Int
    totalItems: Int!
    totalPages: Int!
}
```

#### Вывести сохранённый запрос

Вывести один сохранённый запрос, используя идентификатор окружения и имя запроса:

```graphql

{
savedQuery(environmentId: "123", savedQueryName: "query_name") {
  name
  description
  label
  queryParams {
    metrics {
      name
    }
    groupBy {
      name
      grain
      datePart
    }
    where {
      whereSqlTemplate
    }
  }
}
}
```

### Запросы

При выполнении запросов к данным необходимо указать *либо* `groupBy`, *либо* выборку `metrics`. В следующем разделе приведены примеры того, как выполнять запросы к метрикам:

* [Создать запрос](#create-query)
* [Получить результат запроса](#fetch-query-result)

#### Создать запрос

```graphql
createQuery(
  environmentId: BigInt!
  metrics: [MetricInput!]!
  groupBy: [GroupByInput!] = null
  limit: Int = null
  where: [WhereInput!] = null
  order: [OrderByInput!] = null
): CreateQueryResult
```

```graphql
MetricInput {
  name: String!
  alias: String!
}

GroupByInput {
  name: String!
  grain: TimeGranularity = null
}

WhereInput {
  sql: String!
}

OrderByinput { # -- передайте только один из metric или groupBy
  metric: MetricInput = null
  groupBy: GroupByInput = null
  descending: Boolean! = false
}
```

#### Получение результата запроса

```graphql
query(
  environmentId: BigInt!
  queryId: String!
): QueryResult!
```

GraphQL API использует процесс опроса для запросов, так как запросы могут быть долгими в некоторых случаях. Это работает путем создания запроса с мутацией `createQuery`, которая возвращает идентификатор запроса. Этот идентификатор затем используется для непрерывной проверки (опроса) результатов и статуса вашего запроса. Типичный поток будет выглядеть следующим образом:

1. Запуск запроса

```graphql
mutation {
  createQuery(
    environmentId: 123456
    metrics: [{name: "order_total"}]
    groupBy: [{name: "metric_time"}]
  ) {
    queryId  # => Возвращает 'QueryID_12345678'
  }
}
```

2. Опрос результатов

```graphql
{
  query(environmentId: 123456, queryId: "QueryID_12345678") {
    sql
    status
    error
    totalPages
    jsonResult
    arrowResult
  }
}
```

3. Продолжайте выполнять запрос 2. с соответствующим интервалом, пока статус не станет `FAILED` или `SUCCESSFUL`

### Формат вывода и пагинация

#### Формат вывода

По умолчанию результат возвращается в формате Arrow. Вы можете переключиться на формат JSON с помощью соответствующего параметра. Однако из‑за ограничений производительности мы рекомендуем использовать параметр JSON только для тестирования и валидации.

Получаемый JSON представляет собой строку, закодированную в base64. Чтобы получить доступ к данным, вы можете декодировать её с помощью base64‑декодера. JSON формируется на основе pandas, что означает, что вы можете преобразовать его обратно в dataframe с помощью `pandas.read_json(json, orient="table")`. Либо вы можете работать с данными напрямую через `json["data"]`, а схему таблицы получить из `json["schema"]["fields"]`.

В качестве альтернативы вы можете передать `encoded:false` в поле `jsonResult`, чтобы сразу получить «сырой» JSON‑строку без кодирования.

По умолчанию вывод осуществляется в формате Arrow. Вы можете переключиться на формат JSON, используя следующий параметр. Однако из-за ограничений производительности мы рекомендуем использовать параметр JSON для тестирования и валидации. Полученный JSON является строкой, закодированной в base64. Чтобы получить к нему доступ, вы можете декодировать его с помощью декодера base64. JSON создается из pandas, что означает, что вы можете преобразовать его обратно в dataframe, используя `pandas.read_json(json, orient="table")`. Или вы можете работать с данными напрямую, используя `json["data"]`, и найти схему таблицы, используя `json["schema"]["fields"]`. В качестве альтернативы, вы можете передать `encoded:false` в поле jsonResult, чтобы получить необработанную строку JSON напрямую.

```graphql
{
  query(environmentId: BigInt!, queryId: Int!, pageNum: Int! = 1) {
    sql
    status
    error
    totalPages
    arrowResult
    jsonResult(orient: PandasJsonOrient! = TABLE, encoded: Boolean! = true)
  }
}
```

Результаты по умолчанию выводятся в таблицу, но вы можете изменить это на любое значение, поддерживаемое [pandas](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_json.html).

#### Пагинация

По умолчанию мы возвращаем 1024 строки на страницу. Если ваш набор результатов превышает это значение, вам нужно увеличить номер страницы, используя опцию `pageNum`.

### Запуск запроса на Python

`arrowResult` в ответе на GraphQL-запрос является дампом байтов, который не является визуально полезным. Вы можете преобразовать эти байтовые данные в таблицу Arrow, используя любой язык, поддерживающий Arrow. Обратитесь к следующему примеру на Python, объясняющему, как запросить и декодировать результат arrow:

```python
import base64
import pyarrow as pa
import time

headers = {"Authorization":"Bearer <token>"}
query_result_request = """
{
  query(environmentId: 70, queryId: "12345678") {
    sql
    status
    error
    arrowResult
  }
}
"""

while True:
  gql_response = requests.post(
    "https://semantic-layer.cloud.getdbt.com/api/graphql",
    json={"query": query_result_request},
    headers=headers,
  )
  if gql_response.json()["data"]["status"] in ["FAILED", "SUCCESSFUL"]:
    break
  # Установите соответствующий интервал между запросами опроса
  time.sleep(1)

"""
gql_response.json() => 
{
  "data": {
    "query": {
      "sql": "SELECT\n  ordered_at AS metric_time__day\n  , SUM(order_total) AS order_total\nFROM semantic_layer.orders orders_src_1\nGROUP BY\n  ordered_at",
      "status": "SUCCESSFUL",
      "error": null,
      "arrowResult": "arrow-byte-data"
    }
  }
}
"""

def to_arrow_table(byte_string: str) -> pa.Table:
  """Получите необработанную строку base64 и преобразуйте в таблицу Arrow."""
  with pa.ipc.open_stream(base64.b64decode(byte_string)) as reader:
    return pa.Table.from_batches(reader, reader.schema)


arrow_table = to_arrow_table(gql_response.json()["data"]["query"]["arrowResult"])

# Выполните любую доступную функциональность, например, преобразование в таблицу pandas.
print(arrow_table.to_pandas())
"""
order_total  ordered_at
          3  2023-08-07
        112  2023-08-08
         12  2023-08-09
       5123  2023-08-10
"""
```

### Дополнительные примеры create-запросов

В этом разделе приведены примеры запросов для GraphQL API — например, как выполнять запросы метрик, измерений (dimensions), where-фильтров и других возможностей:

* [Query metric alias](#query-metric-alias) — Запрос с использованием псевдонима метрики, который позволяет использовать более простые или интуитивно понятные имена для метрик вместо их полных определений.
* [Query with a time grain](#query-with-a-time-grain) — Получение нескольких метрик с изменением гранулярности временного измерения.
* [Query multiple metrics and multiple dimensions](#query-multiple-metrics-and-multiple-dimensions) — Выбор общих измерений для нескольких метрик.
* [Query a categorical dimension on its own](#query-a-categorical-dimension-on-its-own) — Группировка по категориальному измерению.
* [Query with a where filter](#query-with-a-where-filter) — Использование параметра `where` для фильтрации по измерениям и сущностям с помощью параметров.
* [Query with order](#query-records) — Запрос с использованием `orderBy`, принимает простую строку, соответствующую Dimension, Metric или Entity. По умолчанию используется сортировка по возрастанию.
* [Query with limit](#query-with-limit) — Запрос с использованием условия `limit`.
* [Query saved queries](#query-saved-queries) — Запрос с использованием сохранённого запроса через параметр `savedQuery` для часто используемых запросов.
* [Query with just compiling SQL](#query-with-just-compiling-sql) — Запрос, использующий ключевое слово compile через мутацию `compileSql`.
* [Query records](#query-records) — Просмотр всех запросов, выполненных в вашем проекте.

#### Псевдоним метрики запроса

```graphql
mutation {
  createQuery(
    environmentId: "123"
    metrics: [{name: "metric_name", alias: "metric_alias"}]
  ) {
    ...
  }
}
```

#### Запрос с временной гранулярностью

```graphql
mutation {
  createQuery(
    environmentId: "123"
    metrics: [{name: "order_total"}]
    groupBy: [{name: "metric_time", grain: MONTH}] 
  ) {
    queryId
  }
}
```

Обратите внимание, что при использовании гранулярности в запросе вывод временного измерения с примененной к нему временной зернистостью всегда принимает форму имени измерения, дополненного двойным подчеркиванием и уровнем гранулярности - `{time_dimension_name}__{DAY|WEEK|MONTH|QUARTER|YEAR}`. Даже если гранулярность не указана, она также всегда будет иметь добавленную гранулярность и по умолчанию будет самой низкой доступной (обычно ежедневной для большинства источников данных). Рекомендуется указывать гранулярность при использовании временных измерений, чтобы не было неожиданных результатов с выходными данными.

#### Запрос нескольких метрик и нескольких измерений

```graphql
mutation {
  createQuery(
    environmentId: "123"
    metrics: [{name: "food_order_amount"}, {name: "order_gross_profit"}]
    groupBy: [{name: "metric_time", grain: MONTH}, {name: "customer__customer_type"}]
  ) {
    queryId
  }
}
```

#### Запрос по категориальному измерению отдельно

```graphql
mutation {
  createQuery(
    environmentId: "123"
    groupBy: [{name: "customer__customer_type"}]
  ) {
    queryId
  }
}
```

#### Запрос с фильтром `where`

Фильтр `where` принимает аргумент списка (или строку для одного ввода). В зависимости от объекта, который вы фильтруете, есть несколько параметров:

* `Dimension()` — Используется для любых категориальных или временных измерений. Например, `Dimension('metric_time').grain('week')` или `Dimension('customer__country')`.

* `Entity()` — Используется для сущностей, таких как первичные и внешние ключи, например, `Entity('order_id')`.

Примечание: Если вы предпочитаете `where`-условие с более явным путем, вы можете опционально использовать `TimeDimension()` для разделения категориальных измерений от временных. Ввод `TimeDimension` принимает временное измерение и, опционально, уровень гранулярности. `TimeDimension('metric_time', 'month')`.

```graphql
mutation {
  createQuery(
    environmentId: "123"
    metrics:[{name: "order_total"}]
    groupBy:[{name: "customer__customer_type"}, {name: "metric_time", grain: month}]
    where:[{sql: "{{ Dimension('customer__customer_type') }} = 'new'"}, {sql:"{{ Dimension('metric_time').grain('month') }} > '2022-10-01'"}]
    ) {
     queryId
    }
}
```

#### Соединения через несколько промежуточных таблиц

В случаях, когда вам нужно выполнить запрос через несколько связанных таблиц (многопереходные соединения), используйте аргумент `entity_path`, чтобы указать путь между связанными сущностями. Вот примеры того, как вы можете определить эти соединения:

* В этом примере вы запрашиваете измерение `location_name`, но указываете, что оно должно быть соединено, используя поле `order_id`.

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

* В этом примере измерение `salesforce_account_owner` соединено с полем `region`, с путем, проходящим через `salesforce_account`.

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

````text
#### Запрос с сортировкой \{#query-with-order}

```graphql
mutation {
  createQuery(
    environmentId: "123"
    metrics: [{name: "order_total"}]
    groupBy: [{name: "metric_time", grain: MONTH}] 
    orderBy: [{metric: {name: "order_total"}}, {groupBy: {name: "metric_time", grain: MONTH}, descending:true}]
  ) {
    queryId
  }
}
````

#### Запрос с ограничением (limit)

```graphql
mutation {
  createQuery(
    environmentId: "123"
    metrics: [{name:"food_order_amount"}, {name: "order_gross_profit"}]
    groupBy: [{name:"metric_time", grain: MONTH}, {name: "customer__customer_type"}]
    limit: 10 
  ) {
    queryId
  }
}
```

#### Запрос сохранённых запросов

Этот вариант принимает те же входные параметры, что и мутация `createQuery`, но дополнительно включает поле `savedQuery`. Вы можете использовать его для часто используемых запросов.

```graphql
mutation {
  createQuery(
    environmentId: "123"
    savedQuery: "new_customer_orders"
  ) {
    queryId
  }
}
```

Примечание о выполнении сохранённых запросов

При выполнении [сохранённых запросов](https://docs.getdbt.tech/docs/build/saved-queries.md) вы можете использовать такие параметры, как `where`, `limit`, `order`, `compile` и другие. Однако имейте в виду, что в этом контексте нельзя обращаться к параметрам `metric` или `group_by`. Это связано с тем, что они являются предопределёнными и фиксированными параметрами для сохранённых запросов, и их нельзя изменить во время выполнения запроса. Если вам нужно запрашивать дополнительные метрики или измерения, вы можете построить запрос, используя стандартный формат.

#### Запрос только с компиляцией SQL

Этот вариант принимает те же входные параметры, что и мутация `createQuery`.

```graphql
mutation {
  compileSql(
    environmentId: "123"
    metrics: [{name:"food_order_amount"} {name:"order_gross_profit"}]
    groupBy: [{name:"metric_time", grain: MONTH}, {name:"customer__customer_type"}]
  ) {
    sql
  }
}
```

#### История запросов

Используйте этот endpoint, чтобы просмотреть все запросы, выполненные в вашем проекте. Сюда входят как запросы Insights, так и запросы Semantic Layer.

```graphql
{
  queryRecords(
    environmentId:123
  ) {
    items {
      queryId
      status
      startTime
      endTime
      connectionDetails
      sqlDialect
      connectionSchema
      error
      queryDetails {
        ... on SemanticLayerQueryDetails {
          params {
            type
            metrics {
              name
            }
            groupBy {
              name
              grain
            }
            limit
            where {
              sql
            }
            orderBy {
              groupBy {
                name
                grain
              }
              metric {
                name
              }
              descending
            }
            savedQuery
          }
        }
        ... on RawSqlQueryDetails {
          queryStr
          compiledSql
          numCols
          queryDescription
          queryTitle
        }
      }
    }
    totalItems
    pageNum
    pageSize
  }
}
```
