使用 GraphQL 進行彙整資料

Data API 建置器支援對 SQL Server 產品系列和 Azure Synapse Analytics 專用 SQL 集區中的實體進行 GraphQL 彙總。 在集合查詢中使用該 groupBy 欄位來計算 sumavgminmaxcount 值。

本文範例使用 SQL Server 與 GraphQL 實體,並取得讀取權限。 Data API 建構器會產生類似每個情境中陳述的 SQL 指令。 參數值會在執行時以查詢參數的形式出現。 sum這些 、 avgminmax 函數適用於數值欄位。 這個 count 函數適用於任何領域。

Important

Azure Cosmos DB for NoSQL、PostgreSQL 或 MySQL 無法提供聚合功能。

聚合預設是啟用的。 若要將其關閉,請在設定檔中的 runtime.graphql 下,將 enable-aggregation 設為 false。 聚合查詢會回傳一頁群組,預設為 100 頁。 在集合查詢中使用 first 參數來改變最大值,例如 books(first: 500)。 預設值設為 runtime.pagination.default-page-size

GraphQL 結構描述的新增內容

啟用聚合後,Data 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) }

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
          }
        }
      ]
    }
  }
}
總頁數 平均頁數 最短的書 最長書籍 書籍數量
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

在檢視中將資料列分組

聚合也適用於以視圖為基礎的實體。 為檢視設定一個鍵欄位,讓 Data 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,在 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 工程 二十九
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 傢俱 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
          }
        }
      ]
    }
  }
}
分類 總值 平均價格
電子產品 15000 7500
傢俱 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]。 不要引用田地名稱。
  • 不要在同一個集合查詢中選擇 itemsgroupBy
  • 當你依欄位分組時,只選擇物件內 fields 相同的欄位。