使用 GraphQL 聚合数据

Data API 生成器支持对 SQL Server 产品系列和 Azure Synapse Analytics 专用 SQL 池实体进行 GraphQL 聚合。 使用集合查询中的 groupBy 字段来计算 sumavgminmaxcount 值。

本文中的示例使用具有读取权限的 SQL Server 和 GraphQL 实体。 Data API 生成器会生成类似于各个场景中所示语句的 SQL。 参数值在运行时显示为查询参数。 和sumavgminmax函数适用于数值字段。 该 count 函数适用于任何字段。

重要

聚合不适用于 NoSQL、PostgreSQL 或 MySQL 的Azure Cosmos DB。

默认情况下启用聚合。 若要将其关闭,请在配置文件中的 runtime.graphql 下将 enable-aggregation 设置为 false。 聚合查询返回一页分组结果,默认返回 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>) } }

sumavgminmax

聚合数值字段。

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) }

数据 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 页面
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
          }
        }
      ]
    }
  }
}
总页数 averagePages 最短图书 longestBook 图书数量
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 页面
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
          }
        }
      ]
    }
  }
}
总页数 averagePages
2023 960 320
2024 900 300

从视图中对行进行分组

聚合也适用于由视图支持的实体。 为视图配置键字段,以便数据 API 生成器可以将其公开为实体。

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 工程 二十九
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
          }
        }
      ]
    }
  }
}
部门 最年轻的 最古老的 员工人数
工程 二十九 45 3
Sales 34 52 2
Support 25 38 2

在聚合之前筛选行

在集合查询上使用 filter ,在数据 API 生成器组并聚合源行之前限制源行。

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 工程 二十九
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;

对于此查询, @param130.

生成的输出

{
  "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 分类 价格
1 电子工业 5000.00
2 电子工业 10000.00
3 Furniture 4000.00
4 Furniture 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
          }
        }
      ]
    }
  }
}
分类 总值 平均价格
电子工业 15000 7500
Furniture 12000 6000

对非重复值进行计数

使用 distinct: truecount 来统计每个组中的唯一值数量。

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 uniqueProducts 订单总数
101 5 6
102 3 4

常见错误

  • 在集合字段中选择 groupBy 。 不要作为集合参数传递 groupBy
  • 使用 aggregations,而不是 aggregates
  • 使用 avg,而不是 average
  • 使用字段枚举值,例如 fields: [year]。 不要给字段名加引号。
  • 不要选择 itemsgroupBy 在同一集合查询中。
  • 按字段分组时,仅选择对象中的 fields 相同字段。