Агрегированные данные с помощью GraphQL

Data API builder поддерживает агрегацию GraphQL для сущностей семейства SQL Server и выделенного пула SQL в Azure Synapse Analytics. Используйте поле groupBy в запросе к коллекции для вычисления значений sum, avg, min, max и count.

В примерах в этой статье используются сущности SQL Server и GraphQL с разрешением на чтение. Data API builder генерирует SQL-запросы, подобные инструкциям, показанным для каждого сценария. Значения параметров отображаются в качестве параметров запроса во время выполнения. Функции sum, avgminи max функции применяются к числовым полям. Функция count работает в любом поле.

Important

Агрегирование недоступно для Azure Cosmos DB для NoSQL, PostgreSQL или MySQL.

Агрегирование по умолчанию включено. Чтобы отключить это, установите enable-aggregation в значение false в разделе runtime.graphql файла конфигурации. Запросы агрегирования возвращают одну страницу групп, 100 по умолчанию. Используйте аргумент first в запросе к коллекции, чтобы изменить максимум, например books(first: 500). Задайте значение по умолчанию runtime.pagination.default-page-size.

Дополнения схемы GraphQL

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

Следующие фрагменты кода показывают форматы синтаксиса, а не полные запросы GraphQL.

groupBy

Возвращает сгруппированные строки для коллекции. Выберите этот элемент вместо items в агрегационном запросе.

<collection> { groupBy { ... } }

groupBy(fields: [...])

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

<collection> { groupBy(fields: [<field>, ...]) { ... } }

fields

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

groupBy(fields: [<field>]) { fields { <field> } }

aggregations

Содержит выбор агрегатной функции для каждой группы.

groupBy { aggregations { <alias>: <function>(field: <field>) } }

sum, avg, min и max

Агрегированные числовые поля.

aggregations { <alias>: sum(field: <numeric-field>) }
aggregations { <alias>: avg(field: <numeric-field>) }
aggregations { <alias>: min(field: <numeric-field>) }
aggregations { <alias>: max(field: <numeric-field>) }

count

Подсчитывает значения для поля.

aggregations { <alias>: count(field: <field>) }

field

Указывает поле сущности для агрегирования. Имена полей — это значения перечисления, а не строки.

<function>(field: <field>)

having

Фильтрует группы после того, как конструктор Data API вычисляет агрегированное значение.

aggregations { <alias>: <function>(field: <field>, having: { <operator>: <value> }) }

distinct

Подсчитывает уникальные значения при использовании с count.

aggregations { <alias>: count(field: <field>, distinct: true) }

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

Samples

В следующих примерах показана таблица SQL, запрос GraphQL, результирующий SQL и результирующий результат для распространенных шаблонов агрегирования.

Агрегировать все строки в таблице

Используйте этот шаблон, если требуется одна сводная строка для всей сущности.

Таблица SQL

CREATE TABLE dbo.Books (
    id INT NOT NULL PRIMARY KEY,
    title NVARCHAR(200) NOT NULL,
    [year] INT NOT NULL,
    pages INT NOT NULL
);

INSERT INTO dbo.Books (id, title, [year], pages) VALUES
    (1, N'GraphQL Basics', 2023, 120),
    (2, N'Advanced APIs', 2023, 450),
    (3, N'Data Patterns', 2023, 390),
    (4, N'Cloud APIs', 2024, 140),
    (5, N'Runtime Internals', 2024, 510),
    (6, N'Query Tuning', 2024, 250);
id title год pages
1 Основы GraphQL 2023 120
2 Расширенные API 2023 450
3 Шаблоны данных 2023 390
4 Облачные API 2024 140
5 Внутренние компоненты среды выполнения 2024 510
6 Настройка запросов 2024 250

Запрос GraphQL

{
  books {
    groupBy {
      aggregations {
        totalPages: sum(field: pages)
        averagePages: avg(field: pages)
        shortestBook: min(field: pages)
        longestBook: max(field: pages)
        bookCount: count(field: id)
      }
    }
  }
}

Результирующий SQL

SELECT TOP 100
    SUM([table0].[pages]) AS [totalPages],
    AVG([table0].[pages]) AS [averagePages],
    MIN([table0].[pages]) AS [shortestBook],
    MAX([table0].[pages]) AS [longestBook],
    COUNT([table0].[id]) AS [bookCount]
FROM [dbo].[Books] AS [table0]
WHERE 1 = 1
FOR JSON PATH, INCLUDE_NULL_VALUES;

Результирующий результат

{
  "data": {
    "books": {
      "groupBy": [
        {
          "aggregations": {
            "totalPages": 1860,
            "averagePages": 310,
            "shortestBook": 120,
            "longestBook": 510,
            "bookCount": 6
          }
        }
      ]
    }
  }
}
общее количество страниц Среднее количество страниц shortestBook longestBook bookCount
1860 310 120 510 6

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

Используйте groupBy(fields: [...]), чтобы возвращать одну агрегированную строку для каждого значения поля. Имена полей — это значения перечисления GraphQL, а не строки.

Таблица SQL

CREATE TABLE dbo.Books (
  id INT NOT NULL PRIMARY KEY,
  title NVARCHAR(200) NOT NULL,
  [year] INT NOT NULL,
  pages INT NOT NULL
);

INSERT INTO dbo.Books (id, title, [year], pages) VALUES
  (1, N'GraphQL Basics', 2023, 120),
  (2, N'Advanced APIs', 2023, 450),
  (3, N'Data Patterns', 2023, 390),
  (4, N'Cloud APIs', 2024, 140),
  (5, N'Runtime Internals', 2024, 510),
  (6, N'Query Tuning', 2024, 250);
id title год pages
1 Основы GraphQL 2023 120
2 Расширенные API 2023 450
3 Шаблоны данных 2023 390
4 Облачные API 2024 140
5 Внутренние компоненты среды выполнения 2024 510
6 Настройка запросов 2024 250

Запрос GraphQL

{
  books(orderBy: { year: ASC }) {
    groupBy(fields: [year]) {
      fields { year }
      aggregations {
        totalPages: sum(field: pages)
        averagePages: avg(field: pages)
      }
    }
  }
}

Результирующий SQL

SELECT TOP 100
    [table0].[year] AS [year],
    SUM([table0].[pages]) AS [totalPages],
    AVG([table0].[pages]) AS [averagePages]
FROM [dbo].[Books] AS [table0]
WHERE 1 = 1
GROUP BY [table0].[year]
ORDER BY [table0].[year] ASC
FOR JSON PATH, INCLUDE_NULL_VALUES;

Результирующий результат

{
  "data": {
    "books": {
      "groupBy": [
        {
          "fields": {
            "year": 2023
          },
          "aggregations": {
            "totalPages": 960,
            "averagePages": 320
          }
        },
        {
          "fields": {
            "year": 2024
          },
          "aggregations": {
            "totalPages": 900,
            "averagePages": 300
          }
        }
      ]
    }
  }
}
год Всего страниц среднее количество страниц
2023 960 320
2024 900 300

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

Агрегирование также работает для сущностей, поддерживаемых представлением. Настройте ключевое поле для представления, чтобы построитель данных смог предоставить его как сущность.

представление SQL

CREATE TABLE dbo.Employees (
  id INT NOT NULL PRIMARY KEY,
  name NVARCHAR(100) NOT NULL,
  department NVARCHAR(50) NOT NULL,
  title NVARCHAR(100) NOT NULL,
  age INT NOT NULL
);

INSERT INTO dbo.Employees (id, name, department, title, age) VALUES
  (1, N'Ada', N'Engineering', N'Developer', 29),
  (2, N'Ben', N'Engineering', N'Architect', 41),
  (3, N'Cora', N'Sales', N'Account manager', 34),
  (4, N'Diego', N'Sales', N'Sales lead', 52),
  (5, N'Ema', N'Support', N'Support engineer', 25),
  (6, N'Finn', N'Support', N'Support lead', 38),
  (7, N'Gia', N'Engineering', N'Engineering manager', 45);

CREATE VIEW dbo.EmployeeAgeReport
AS
SELECT id, department, age
FROM dbo.Employees;
id отдел возраст
1 Инженерия 29
2 Инженерия 41
3 Sales 34
4 Sales 52
5 Support 25
6 Support 38
7 Инженерия 45

Настройте представление, используя id в качестве ключевого поля:

dab add EmployeeAgeReport --source dbo.EmployeeAgeReport --source.type view --source.key-fields id --permissions "anonymous:read"

Запрос GraphQL

{
  employeeAgeReports(orderBy: { department: ASC }) {
    groupBy(fields: [department]) {
      fields { department }
      aggregations {
        youngest: min(field: age)
        oldest: max(field: age)
        employeeCount: count(field: id)
      }
    }
  }
}

Результирующий SQL

SELECT TOP 100
  [table0].[department] AS [department],
  MIN([table0].[age]) AS [youngest],
  MAX([table0].[age]) AS [oldest],
  COUNT([table0].[id]) AS [employeeCount]
FROM [dbo].[EmployeeAgeReport] AS [table0]
WHERE 1 = 1
GROUP BY [table0].[department]
ORDER BY [table0].[department] ASC
FOR JSON PATH, INCLUDE_NULL_VALUES;

Результирующий результат

{
  "data": {
    "employeeAgeReports": {
      "groupBy": [
        {
          "fields": {
            "department": "Engineering"
          },
          "aggregations": {
            "youngest": 29,
            "oldest": 45,
            "employeeCount": 3
          }
        },
        {
          "fields": {
            "department": "Sales"
          },
          "aggregations": {
            "youngest": 34,
            "oldest": 52,
            "employeeCount": 2
          }
        },
        {
          "fields": {
            "department": "Support"
          },
          "aggregations": {
            "youngest": 25,
            "oldest": 38,
            "employeeCount": 2
          }
        }
      ]
    }
  }
}
отдел Младший старейший Количество сотрудников
Инженерия 29 45 3
Sales 34 52 2
Support 25 38 2

Фильтрация строк перед агрегированием

Используйте filter в запросе коллекции, чтобы ограничить исходные строки до того, как Data API builder сгруппирует и агрегирует их.

представление SQL

CREATE TABLE dbo.Employees (
  id INT NOT NULL PRIMARY KEY,
  name NVARCHAR(100) NOT NULL,
  department NVARCHAR(50) NOT NULL,
  title NVARCHAR(100) NOT NULL,
  age INT NOT NULL
);

INSERT INTO dbo.Employees (id, name, department, title, age) VALUES
  (1, N'Ada', N'Engineering', N'Developer', 29),
  (2, N'Ben', N'Engineering', N'Architect', 41),
  (3, N'Cora', N'Sales', N'Account manager', 34),
  (4, N'Diego', N'Sales', N'Sales lead', 52),
  (5, N'Ema', N'Support', N'Support engineer', 25),
  (6, N'Finn', N'Support', N'Support lead', 38),
  (7, N'Gia', N'Engineering', N'Engineering manager', 45);

CREATE VIEW dbo.EmployeeAgeReport
AS
SELECT id, department, age
FROM dbo.Employees;
id отдел возраст
1 Инженерия 29
2 Инженерия 41
3 Sales 34
4 Sales 52
5 Support 25
6 Support 38
7 Инженерия 45

Запрос GraphQL

{
  employeeAgeReports(filter: { age: { gt: 30 } }, orderBy: { department: ASC }) {
    groupBy(fields: [department]) {
      fields { department }
      aggregations {
        youngest: min(field: age)
        oldest: max(field: age)
        employeeCount: count(field: id)
      }
    }
  }
}

Результирующий SQL

SELECT TOP 100
  [table0].[department] AS [department],
  MIN([table0].[age]) AS [youngest],
  MAX([table0].[age]) AS [oldest],
  COUNT([table0].[id]) AS [employeeCount]
FROM [dbo].[EmployeeAgeReport] AS [table0]
WHERE [table0].[age] > @param1
GROUP BY [table0].[department]
ORDER BY [table0].[department] ASC
FOR JSON PATH, INCLUDE_NULL_VALUES;

Для этого запроса @param1 — это 30.

Результирующий результат

{
  "data": {
    "employeeAgeReports": {
      "groupBy": [
        {
          "fields": {
            "department": "Engineering"
          },
          "aggregations": {
            "youngest": 41,
            "oldest": 45,
            "employeeCount": 2
          }
        },
        {
          "fields": {
            "department": "Sales"
          },
          "aggregations": {
            "youngest": 34,
            "oldest": 52,
            "employeeCount": 2
          }
        },
        {
          "fields": {
            "department": "Support"
          },
          "aggregations": {
            "youngest": 38,
            "oldest": 38,
            "employeeCount": 1
          }
        }
      ]
    }
  }
}
отдел Младший старейший Количество сотрудников
Инженерия 41 45 2
Sales 34 52 2
Support 38 38 1

Фильтрация групп с помощью having

Используйте having с агрегатной функцией, чтобы фильтровать группы после агрегации. Этот шаблон соответствует выражению SQL HAVING.

Таблица SQL

CREATE TABLE dbo.Products (
    id INT NOT NULL PRIMARY KEY,
    category NVARCHAR(50) NOT NULL,
    price DECIMAL(10,2) NOT NULL
);

INSERT INTO dbo.Products (id, category, price) VALUES
    (1, N'Electronics', 5000.00),
    (2, N'Electronics', 10000.00),
    (3, N'Furniture', 4000.00),
    (4, N'Furniture', 8000.00),
    (5, N'Books', 100.00),
    (6, N'Books', 200.00);
id category ст-ти
1 Электроника 5000.00
2 Электроника 10000,00
3 Мебель 4000.00
4 Мебель 8000.00
5 Книги 100.00
6 Книги 200.00

Запрос GraphQL

{
  products(orderBy: { category: ASC }) {
    groupBy(fields: [category]) {
      fields { category }
      aggregations {
        totalValue: sum(field: price, having: { gt: 10000 })
        averagePrice: avg(field: price)
      }
    }
  }
}

Результирующий SQL

SELECT TOP 100
    [table0].[category] AS [category],
    SUM([table0].[price]) AS [totalValue],
    AVG([table0].[price]) AS [averagePrice]
FROM [dbo].[Products] AS [table0]
WHERE 1 = 1
GROUP BY [table0].[category]
HAVING SUM([table0].[price]) > @param1
ORDER BY [table0].[category] ASC
FOR JSON PATH, INCLUDE_NULL_VALUES;

Для этого запроса @param110000.

Результирующий результат

{
  "data": {
    "products": {
      "groupBy": [
        {
          "fields": {
            "category": "Electronics"
          },
          "aggregations": {
            "totalValue": 15000,
            "averagePrice": 7500
          }
        },
        {
          "fields": {
            "category": "Furniture"
          },
          "aggregations": {
            "totalValue": 12000,
            "averagePrice": 6000
          }
        }
      ]
    }
  }
}
category Общее значение средняя цена
Электроника 15000 7500
Мебель 12 000 6000

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

Используйте distinct: true вместе с count для подсчета уникальных значений в каждой группе.

Таблица SQL

CREATE TABLE dbo.Orders (
    id INT NOT NULL PRIMARY KEY,
    customer_id INT NOT NULL,
    product_id INT NOT NULL
);

INSERT INTO dbo.Orders (id, customer_id, product_id) VALUES
    (1, 101, 1),
    (2, 101, 2),
    (3, 101, 2),
    (4, 101, 3),
    (5, 101, 4),
    (6, 101, 5),
    (7, 102, 1),
    (8, 102, 1),
    (9, 102, 2),
    (10, 102, 3);
id customer_id product_id
1 101 1
2 101 2
3 101 2
4 101 3
5 101 4
6 101 5
7 102 1
8 102 1
9 102 2
10 102 3

Запрос GraphQL

{
  orders(orderBy: { customer_id: ASC }) {
    groupBy(fields: [customer_id]) {
      fields { customer_id }
      aggregations {
        uniqueProducts: count(field: product_id, distinct: true)
        totalOrders: count(field: id)
      }
    }
  }
}

Результирующий SQL

SELECT TOP 100
    [table0].[customer_id] AS [customer_id],
    COUNT(DISTINCT ([table0].[product_id])) AS [uniqueProducts],
    COUNT([table0].[id]) AS [totalOrders]
FROM [dbo].[Orders] AS [table0]
WHERE 1 = 1
GROUP BY [table0].[customer_id]
ORDER BY [table0].[customer_id] ASC
FOR JSON PATH, INCLUDE_NULL_VALUES;

Результирующий результат

{
  "data": {
    "orders": {
      "groupBy": [
        {
          "fields": {
            "customer_id": 101
          },
          "aggregations": {
            "uniqueProducts": 5,
            "totalOrders": 6
          }
        },
        {
          "fields": {
            "customer_id": 102
          },
          "aggregations": {
            "uniqueProducts": 3,
            "totalOrders": 4
          }
        }
      ]
    }
  }
}
customer_id уникальные товары Всего заказов
101 5 6
102 3 4

Распространенные ошибки

  • Выберите groupBy внутри поля коллекции. Не передайте groupBy в качестве аргумента коллекции.
  • Используйте aggregations, а не aggregates.
  • Используйте avg, а не average.
  • Используйте значения перечисления полей, например fields: [year]. Не цитируйте имена полей.
  • Не выбирайте items и groupBy в одном запросе коллекции.
  • При группировке по полям выберите только те же поля внутри fields объекта.