データ API ビルダー (DAB) では、SQL ファミリ データベースと Azure Synapse Analytics (専用 SQL プール) の GraphQL 集計とグループ化がサポートされています。 集計を使用すると、カスタム API コードを記述せずに数値フィールドを集計し、結果をグループ化できます。 集計と groupBy は、Azure Cosmos DB for NoSQL、PostgreSQL、または MySQL では使用できません。
[前提条件]
- サポートされているデータベース:
- SQL Server 2016 以降
- Azure SQL Database
- Azure SQL Managed Instance
- Microsoft Fabric SQL
- Azure Synapse Analytics (専用 SQL プールのみ)
- データ API ビルダー CLI。 CLI をインストールする
- GraphQLを通してエンティティが公開されているDAB構成ファイル。
- クエリを実行する GraphQL クライアント (バナナ ケーキ ポップや GraphQL プレイグラウンドなど)。
サポートされるデータベース
| データベース | 集計のサポート |
|---|---|
| SQL Server / Azure SQL / Microsoft Fabric SQL | ✅ はい |
| Azure Synapse (専用 SQL プール) | ✅ はい |
| Azure Synapse (サーバーレス SQL プール) | ❌ いいえ |
| PostgreSQL | ❌ いいえ |
| MySQL | ❌ いいえ |
| Azure Cosmos DB for NoSQL | ❌ いいえ |
集計関数
DAB では、次の集計関数がサポートされています。
| 機能 | 対象 | Description |
|---|---|---|
sum |
数値フィールドのみ | すべての値の合計 |
average |
数値フィールドのみ | すべての値の平均 |
min |
数値フィールドのみ | 最小値 |
max |
数値フィールドのみ | 最大値 |
count |
任意のフィールド | null 以外の値の数 |
Constraints
-
sum、average、min、およびmaxは、数値データ型 (int、decimal、float など) でのみ機能します。 -
countは、文字列や日付を含む任意のデータ型で機能します。 - テーブルに数値列がない場合、DAB はそのエンティティの集計ノードを生成しません。 数値以外のフィールドでも
countを使用できます。
省略可能な修飾子
| 修飾子 | 目的 | Example |
|---|---|---|
distinct: true |
一意の値のみをカウントする | 個別の顧客をカウントする |
having: { ... } |
集計後にグループをフィルター処理する | 合計が 1,000 > グループを表示する |
DAB ランタイムを実行する
GraphQL エンドポイントを使用できるように、構成ファイルで DAB を起動します。
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しないと、すべての行が 1 つの結果に折りたたまれるため、応答は (配列ではなく) 1 つのオブジェクトを返します。
1 つ以上のフィールドでグループ化する
行を 1 つ以上の列でグループ化し、グループごとに集計を返します。
テーブル スキーマ
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 フィルターは、集計関数に個別に適用されます。 "sum > 1000 OR count < 10" のようなクロス集計条件を 1 つの 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 }
]
}
}
}
}
最初の count ( distinct: true) は、顧客ごとに一意の製品を返します。 2 番目の count は、合計注文数を返します。
注
同じフィールドに対して複数の集計を要求すると、DAB は要求された順序で集計を返します。 エイリアス (たとえば、 uniqueProducts: count(distinct: true)) を使用して、応答を自己文書化します。
フィルターと集計を組み合わせる
グループ化する前に行に filter を適用し、集計後にグループに having します。 操作の順序を理解することは重要です。
-
フィルター (SQL
WHERE) は、グループ化する前に行を削除します - グループ化 残りの行をグループにまとめる
- 集計 では、グループあたりの合計/avg/min/max/count が計算されます
- `Having は 条件に一致しないグループを削除する`
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 専用 SQL プールにのみ適用されます。 - 集計は数値フィールドで実行されます。
countは任意のフィールドで動作します。 数値列のないテーブルは、countのみを公開します。 - グループ化は、同じエンティティのフィールドに適用されます (エンティティ間の groupBy はありません)。
- 大規模な集計にはコストがかかる場合があります。可能であれば、グループ化する前に groupBy 列のインデックスを作成し、行をフィルター処理します。
- クエリのパフォーマンスを向上させるために、頻繁に使用される
groupBy列にインデックスを作成します。
トラブルシューティング
エラー: フィールドは集計をサポートしていません
原因: 数値以外のフィールドで sum、 average、 min、または max を使用する。
解決策:
- スキーマのイントロスペクションを使用して、フィールド型を確認します。
- 数値以外のフィールドには
countを使用します。 - ユーザー設定フィールド名を使用している場合は、フィールド マッピングを確認します。
エラー: 集計ノードが見つかりません
原因: エンティティに数値列がありません。
解決策:
- テーブル スキーマに少なくとも 1 つの数値列があることを確認します。
- 必要に応じて、数値以外のフィールドで
count集計を使用します。
低速集計クエリ
原因: 適切なインデックスのない大きなテーブル。
解決策:
-
groupBy列にインデックスを作成します。 - 集計前に行を制限するには、
filterを使用します。 -
havingを使用して、返されるグループの数を減らします。