Поделиться через


Агрегация данных с помощью GraphQL в среде построителя API данных

Построитель API данных (DAB) поддерживает агрегирование и группирование в GraphQL для баз данных семейства SQL и Azure Synapse Analytics (выделенный пул SQL). Агрегаты позволяют суммировать числовые поля и результаты группы без написания пользовательского кода API. Агрегирование и groupBy недоступно для Azure Cosmos DB для NoSQL, PostgreSQL или MySQL.

Предпосылки

  • Поддерживаемая база данных:
    • SQL Server 2016 или более поздней версии.
    • База данных SQL Azure
    • Управляемый экземпляр SQL Azure
    • Microsoft Fabric SQL
    • Azure Synapse Analytics (только выделенный пул SQL)
  • CLI инструмента построения API данных. Установка интерфейса командной строки
  • Файл конфигурации DAB с сущностью, доступной через GraphQL.
  • Клиент GraphQL (например, Banana Cake Pop или GraphQL Playground) для выполнения запросов.

Поддерживаемые базы данных

База данных Поддержка агрегирования
SQL Server / Azure SQL / Microsoft Fabric SQL ✅ Да
Azure Synapse (выделенный пул SQL) ✅ Да
Azure Synapse (бессерверный пул SQL) ❌ Нет
PostgreSQL ❌ Нет
MySQL ❌ Нет
Azure Cosmos DB для NoSQL ❌ Нет

Агрегатные функции

DAB поддерживает следующие агрегатные функции:

Функция Применимо к Description
sum Числовые поля только Общее количество всех значений
average Числовые поля только Среднее значение всех значений
min Числовые поля только Минимальное значение
max Числовые поля только Максимальное значение
count Любое поле Количество непустых значений

Constraints

  • sum, , averageminи max только работают с числовыми типами данных (int, decimal, float и т. д.).
  • count работает с любым типом данных, включая строки и даты.
  • Если в таблице нет числовых столбцов, DAB не создает узлы агрегирования для этой сущности. Вы по-прежнему можете использовать count в нечисловых полях.

Необязательные модификаторы

Модификатор Цель Example
distinct: true Подсчитывать только уникальные значения Подсчет отдельных клиентов
having: { ... } Фильтрация групп после агрегирования Отображение групп с суммой > 1000

Запуск среды выполнения DAB

Запустите DAB с файлом конфигурации, чтобы конечная точка GraphQL была доступна.

dab start

Агрегированные результаты запроса

В этом разделе приведен полный пример схемы таблицы, запроса GraphQL, созданного SQL и ответа JSON.

Схема таблицы

CREATE TABLE books (
    id INT PRIMARY KEY,
    title NVARCHAR(200),
    year INT,
    pages INT
);

Запрос GraphQL

Используйте GraphQL для группирования строк и возврата статистических значений для числовых полей.

{
  books(
    groupBy: { fields: ["year"] }
  ) {
    items {
      year
    }
    aggregates {
      pages {
        sum
        average
        min
        max
      }
    }
  }
}
  • groupBy.fields группирует строки по указанным столбцам.
  • aggregates предоставляет агрегатные функции для числовых полей (например, pages).
  • Схема GraphQL предоставляет статистические данные только для полей, поддерживающих их; используйте интроспекцию схемы в клиенте для подтверждения доступных статистических полей и функций.

Созданный SQL

DAB преобразует запрос GraphQL в T-SQL:

SELECT 
    [year],
    SUM([pages]) AS [sum],
    AVG([pages]) AS [average],
    MIN([pages]) AS [min],
    MAX([pages]) AS [max]
FROM [dbo].[books]
GROUP BY [year]
FOR JSON PATH, INCLUDE_NULL_VALUES

Ответ в формате JSON

{
  "data": {
    "books": {
      "items": [
        { "year": 2023 },
        { "year": 2024 }
      ],
      "aggregates": {
        "pages": [
          { "sum": 3200, "average": 320, "min": 120, "max": 450 },
          { "sum": 4500, "average": 300, "min": 140, "max": 510 }
        ]
      }
    }
  }
}

Массивы items и aggregates выровнены по индексам — первый элемент в aggregates.pages соответствует первой группе в items.

Агрегирование без группировки

Вычисляйте агрегаты во всех строках, если вы опускаете groupBy.

Запрос GraphQL

{
  books {
    aggregates {
      pages {
        sum
        average
        min
        max
        count
      }
      id {
        count
      }
    }
  }
}

Созданный SQL

SELECT
    SUM([pages]) AS [sum],
    AVG([pages]) AS [average],
    MIN([pages]) AS [min],
    MAX([pages]) AS [max],
    COUNT([pages]) AS [count],
    COUNT([id]) AS [count]
FROM [dbo].[books]
FOR JSON PATH, INCLUDE_NULL_VALUES

Ответ в формате JSON

{
  "data": {
    "books": {
      "aggregates": {
        "pages": {
          "sum": 15420,
          "average": 308,
          "min": 120,
          "max": 850,
          "count": 50
        },
        "id": {
          "count": 50
        }
      }
    }
  }
}

Без groupBy этого ответ возвращает один объект (а не массив), так как все строки объединяются в один результат.

Группировать по одному или нескольким полям

Группировать строки по одному или нескольким столбцам и возвращать суммарные значения для каждой группы.

Схема таблицы

CREATE TABLE sales (
    id INT PRIMARY KEY,
    year INT,
    category NVARCHAR(50),
    revenue DECIMAL(10,2),
    quantity INT
);

Запрос GraphQL

{
  sales(
    groupBy: { fields: ["year", "category"] }
  ) {
    items {
      year
      category
    }
    aggregates {
      revenue {
        sum
        average
      }
      quantity {
        sum
      }
    }
  }
}

Созданный SQL

SELECT
    [year],
    [category],
    SUM([revenue]) AS [sum],
    AVG([revenue]) AS [average],
    SUM([quantity]) AS [sum]
FROM [dbo].[sales]
GROUP BY [year], [category]
FOR JSON PATH, INCLUDE_NULL_VALUES

Ответ в формате JSON

{
  "data": {
    "sales": {
      "items": [
        { "year": 2023, "category": "Books" },
        { "year": 2023, "category": "Electronics" },
        { "year": 2024, "category": "Books" }
      ],
      "aggregates": {
        "revenue": [
          { "sum": 45000.00, "average": 150.00 },
          { "sum": 120000.00, "average": 600.00 },
          { "sum": 52000.00, "average": 173.33 }
        ],
        "quantity": [
          { "sum": 300 },
          { "sum": 200 },
          { "sum": 300 }
        ]
      }
    }
  }
}

Ответ возвращает массивы для items и агрегатов в том же порядке, чтобы можно было выровнять группы с агрегированными значениями.

Необходимость фильтрации агрегированных результатов

Используется having для фильтрации групп после агрегирования. Это эквивалентно предложению SQL HAVING .

Схема таблицы

CREATE TABLE products (
    id INT PRIMARY KEY,
    category NVARCHAR(50),
    price DECIMAL(10,2)
);

Запрос GraphQL

{
  products(
    groupBy: { fields: ["category"] }
  ) {
    items { category }
    aggregates {
      price {
        sum(having: { gt: 10000 })
        average
      }
    }
  }
}

Созданный SQL

SELECT
    [category],
    SUM([price]) AS [sum],
    AVG([price]) AS [average]
FROM [dbo].[products]
GROUP BY [category]
HAVING SUM([price]) > 10000
FOR JSON PATH, INCLUDE_NULL_VALUES

Ответ в формате JSON

Возвращаются только категории, в которых сумма превышает 10000:

{
  "data": {
    "products": {
      "items": [
        { "category": "Electronics" },
        { "category": "Furniture" }
      ],
      "aggregates": {
        "price": [
          { "sum": 15000.00, "average": 300.00 },
          { "sum": 12000.00, "average": 400.00 }
        ]
      }
    }
  }
}

Операторы HAVING

Operator Эквивалент SQL Example
eq = having: { eq: 100 }
neq <> having: { neq: 0 }
gt > having: { gt: 1000 }
gte >= having: { gte: 500 }
lt < having: { lt: 100 }
lte <= having: { lte: 50 }

Замечание

Каждый having фильтр применяется независимо к своей агрегатной функции. Невозможно создать межрегистрационные условия, такие как "сумма > 1000 OR count < 10" в одном запросе GraphQL.

DISTINCT в агрегациях

Подсчет уникальных значений с distinct: true.

Схема таблицы

CREATE TABLE orders (
    id INT PRIMARY KEY,
    customer_id INT,
    product_id INT
);

Запрос GraphQL

{
  orders(
    groupBy: { fields: ["customer_id"] }
  ) {
    items { customer_id }
    aggregates {
      product_id {
        count(distinct: true)
        count
      }
    }
  }
}

Созданный SQL

SELECT
    [customer_id],
    COUNT(DISTINCT [product_id]) AS [count],
    COUNT([product_id]) AS [count]
FROM [dbo].[orders]
GROUP BY [customer_id]
FOR JSON PATH, INCLUDE_NULL_VALUES

Ответ в формате JSON

{
  "data": {
    "orders": {
      "items": [
        { "customer_id": 101 },
        { "customer_id": 102 }
      ],
      "aggregates": {
        "product_id": [
          { "count": 5 },
          { "count": 3 }
        ]
      }
    }
  }
}

Первый countdistinct: true) возвращает уникальные продукты для каждого клиента. count Второй возвращает общее количество заказов.

Замечание

При запросе нескольких агрегатов в одном поле DAB возвращает их в запрошенном порядке. Используйте псевдонимы (например, uniqueProducts: count(distinct: true)для самостоятельного документирования ответов).

Объединение фильтров с агрегированием

Примените filter к строкам перед группировкой и having к группам после агрегирования. Понимание порядка операций имеет решающее значение:

  1. Фильтр (SQL WHERE) удаляет строки перед группировкой
  2. Group by собирает оставшиеся строки в группы
  3. Агрегат вычисляет сумму/среднее/минимум/максимум/количество на группу
  4. Удаление групп, которые не соответствуют условию

Запрос GraphQL

{
  sales(
    filter: { year: { gte: 2023 } }
    groupBy: { fields: ["region"] }
  ) {
    items { region }
    aggregates {
      revenue { sum average }
    }
  }
}

Созданный SQL

SELECT
    [region],
    SUM([revenue]) AS [sum],
    AVG([revenue]) AS [average]
FROM [dbo].[sales]
WHERE [year] >= 2023
GROUP BY [region]
FOR JSON PATH, INCLUDE_NULL_VALUES

Подсказка

Используется filter для исключения строк перед агрегированием. Используется having для фильтрации групп после агрегирования.

Использование псевдонимов с агрегациями

Создание значимых имен полей с помощью псевдонимов GraphQL.

{
  products(
    groupBy: { fields: ["category"] }
  ) {
    items { category }
    aggregates {
      price {
        totalRevenue: sum
        avgPrice: average
        cheapest: min
        mostExpensive: max
        productCount: count
      }
    }
  }
}

Интроспектация схемы

Используйте интроспекцию, чтобы узнать, какие агрегаты доступны для сущности.

{
  __type(name: "BooksAggregates") {
    fields {
      name
      type { name }
    }
  }
}

Числовые поля предоставляют sum, average, min, max и count. Нечисловые поля показывают count.

Советы и ограничения

  • Агрегирование и groupBy применяются только к SQL Server, Azure SQL, Microsoft Fabric SQL и выделенному пулу Azure Synapse Dedicated SQL.
  • Агрегатные функции работают с числовыми полями; count работает с любым полем. Таблицы без числовых столбцов заявляют только count.
  • Группирование применяется к полям в одной сущности (без группы между сущностами).
  • Крупные агрегаты могут быть дорогостоящими. Индексируйте столбцы 'groupBy' и фильтруйте строки перед группировкой, если это возможно.
  • Создавайте индексы для часто используемых groupBy столбцов для повышения производительности запросов.

Устранение неполадок

Ошибка: поле не поддерживает агрегирование

Причина: использование sum, averageminили max в нечисловом поле.

Решение.

  • Используйте интроспекцию схемы для проверки типов полей.
  • Используется count для нечисловых полей.
  • Проверьте сопоставления полей при использовании пользовательских наименований полей.

Ошибка: узлы агрегирования не найдены

Причина. Сущность не имеет числовых столбцов.

Решение.

  • Убедитесь, что схема таблицы имеет по крайней мере один числовой столбец.
  • При необходимости используйте агрегаты count для нечисловых полей.

Медленные запросы на агрегацию

Причина: большие таблицы без правильных индексов.

Решение.

  • Создайте индексы для groupBy столбцов.
  • Используйте filter для ограничения строк перед агрегированием.
  • Используйте having, чтобы уменьшить число возвращаемых групп.

Следующий шаг