Data API 生成器支持对 SQL Server 产品系列和 Azure Synapse Analytics 专用 SQL 池实体进行 GraphQL 聚合。 使用集合查询中的 groupBy 字段来计算 sum、avg、min、max 和 count 值。
本文中的示例使用具有读取权限的 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>) } }
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) }
数据 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;
对于此查询, @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 | 分类 | 价格 |
|---|---|---|
| 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;
对于此查询, @param1 为 10000.
生成的输出
{
"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: 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 | uniqueProducts | 订单总数 |
|---|---|---|
| 101 | 5 | 6 |
| 102 | 3 | 4 |
常见错误
- 在集合字段中选择
groupBy。 不要作为集合参数传递groupBy。 - 使用
aggregations,而不是aggregates。 - 使用
avg,而不是average。 - 使用字段枚举值,例如
fields: [year]。 不要给字段名加引号。 - 不要选择
items并groupBy在同一集合查询中。 - 按字段分组时,仅选择对象中的
fields相同字段。