はじめに
第 5 回 ではガントチャートの日付範囲クエリを解説しました。
本記事(第 6 回)では バーンダウンチャート(BurnDown) の SQL を解説します。現在レコードと履歴レコードを UNION で取得する独特のクエリ構造が特徴です。
バージョン 1.5.2.0 を対象にしています
| 回 | タイトル |
|---|---|
| 第 1 回 | アーキテクチャ編 ── SQL を組み立てる仕組みの全体像 |
| 第 2 回 | 一覧編 ── フィルタとソートの WHERE・ORDER BY |
| 第 3 回 | 編集編 ── SELECT・INSERT・UPDATE の実装 |
| 第 4 回 | カレンダー編 ── 日付範囲クエリの実装 |
| 第 5 回 | ガントチャート編 ── GanttUtilities.Where() と GanttLimit |
| 第 6 回(本記事) | バーンダウンチャート編 ── 現在 + 履歴 UNION クエリ |
| 第 7 回 | 時系列チャート編 ── HorizontalAxis と NormalAndHistory |
| 第 8 回 | カンバン編 ── KambanLimit・全件取得・カード移動 |
| 第 9 回 | クロス集計編 ── GROUP BY と日付軸グループ化式 |
| 第 10 回 | 画像ライブラリ編 ── Binaries INNER JOIN クエリ |
| 第 11 回 | 分析チャート編 ── 分析パーツごとの現在+履歴 SQL |
処理フロー
GET /items/{siteId}/burndown
└─ IssueUtilities.BurnDown() ← L9845 IssueUtilities.cs
├─ InRange(context, ss, view, BurnDownLimit) ← L11094 IssueUtilities.cs
│ └─ Rds.SelectIssues(COUNT のみ,
│ WHERE: フィルタ)
│ ① SELECT COUNT(件数チェック)
└─(inRange=true の場合のみ)
└─ HtmlBuilder.BurnDown()
└─ BurnDownDataRows() ← L9974 IssueUtilities.cs
├─ Rds.SelectIssues(通常テーブル)
│ ② SELECT(現在レコード)
└─ UNION Rds.SelectIssues(HistoryWithoutFlag)
③ SELECT(履歴レコード)
カンバンと同じ InRange による件数チェックで上限(BurnDownLimit)を超えた場合はデータ取得をスキップします。
BurnDown() の実装
BurnDown() は InRange でチェックしてから BurnDownDataRows() を呼び出します。
public static string BurnDown(Context context, SiteSettings ss)
{
// ビューモードが有効か確認
if (!ss.EnableViewMode(context: context, name: "BurnDown"))
{
return HtmlTemplates.Error(context, new ErrorData(type: Error.Types.HasNotPermission));
}
var view = Views.GetBySession(context: context, ss: ss);
// ① 件数チェック(カンバンと同じ InRange を使用)
var inRange = InRange(
context: context,
ss: ss,
view: view,
limit: Parameters.General.BurnDownLimit); // BurnDownLimit を使用
if (!inRange)
{
SessionUtilities.Set(context, message: Messages.TooManyCases(
context, data: Parameters.General.BurnDownLimit.ToString()));
}
return hb.ViewModeTemplate(..., viewModeBody: () =>
{
if (inRange)
{
hb.BurnDown(
context: context,
ss: ss,
// ② データ取得
dataRows: BurnDownDataRows(context: context, ss: ss, view: view),
ownerLabelText: ss.GetColumn(context, "Owner").GridLabelText,
column: ss.GetColumn(context, "WorkValue"));
}
});
}
BurnDownDataRows() の実装
BurnDownDataRows() は 現在レコード と 履歴レコード の 2 本の SQL を UNION で結合して返します。
private static EnumerableRowCollection<DataRow> BurnDownDataRows(
Context context, SiteSettings ss, View view)
{
var where = view.Where(context: context, ss: ss);
var param = view.Param(context: context, ss: ss);
var join = ss.Join(context: context, join: where);
return Repository.ExecuteTable(context, statements: new SqlStatement[]
{
// ② 現在レコード(通常テーブル)
Rds.SelectIssues(
column: Rds.IssuesTitleColumn(context, ss)
.IssueId()
.Ver()
.Title()
.WorkValue()
.StartTime()
.CompletionTime()
.ProgressRate()
.Status()
.Updator()
.CreatedTime()
.UpdatedTime(),
join: join,
where: where,
param: param),
// ③ 履歴レコード(UNION で結合)
Rds.SelectIssues(
unionType: Sqls.UnionTypes.Union, // UNION(重複除去)
tableType: Sqls.TableTypes.HistoryWithoutFlag, // Issues_history テーブル
column: Rds.IssuesTitleColumn(context, ss)
.IssueId(_as: "Id")
.Ver()
.Title()
.WorkValue()
.StartTime()
.CompletionTime()
.ProgressRate()
.Status()
.Updator()
.CreatedTime()
.UpdatedTime(),
join: join,
// WHERE: 現在フィルタに一致する IssueId の履歴レコードのみ
where: Rds.IssuesWhere()
.IssueId_In(sub: Rds.SelectIssues(
column: Rds.IssuesColumn().IssueId(),
where: where)),
param: param,
orderBy: Rds.IssuesOrderBy()
.IssueId() // IssueId 昇順
.Ver()) // バージョン昇順
}).AsEnumerable();
}
発行される SQL のイメージ
-- ② 現在レコード SELECT(通常テーブル)
SELECT
"Issues"."IssueId",
"Issues"."Ver",
"Issues"."Title",
"Issues"."WorkValue",
"Issues"."StartTime",
"Issues"."CompletionTime",
"Issues"."ProgressRate",
"Issues"."Status",
"Issues"."Updator",
"Issues"."CreatedTime",
"Issues"."UpdatedTime"
FROM "Issues"
INNER JOIN "Items" ON "Items"."ReferenceId" = "Issues"."IssueId"
AND "Items"."Ver" = (
SELECT MAX("NItems"."Ver") FROM "Items" "NItems"
WHERE "NItems"."ReferenceId" = "Issues"."IssueId")
WHERE
"Issues"."TenantId" = 1
AND "Issues"."SiteId" = 42
AND ...(画面フィルタ条件)
UNION
-- ③ 履歴レコード SELECT(Issues_history テーブル)
SELECT
"Issues_history"."IssueId",
"Issues_history"."Ver",
"Issues_history"."Title",
"Issues_history"."WorkValue",
"Issues_history"."StartTime",
"Issues_history"."CompletionTime",
"Issues_history"."ProgressRate",
"Issues_history"."Status",
"Issues_history"."Updator",
"Issues_history"."CreatedTime",
"Issues_history"."UpdatedTime"
FROM "Issues_history"
INNER JOIN "Items" ON ...(同じ JOIN)
WHERE
"Issues_history"."IssueId" IN (
SELECT "Issues"."IssueId"
FROM "Issues"
INNER JOIN "Items" ON ...
WHERE "Issues"."TenantId" = 1
AND "Issues"."SiteId" = 42
AND ...(同じフィルタ条件)
)
ORDER BY "Issues_history"."IssueId" ASC, "Issues_history"."Ver" ASC
UNION(UNION ALL ではなく UNION)を使うため、現在レコードと履歴レコードが完全に同一内容の行は重複排除されます。ただし通常は Ver が異なるため重複は発生しません。
UNION の目的:バーンダウン計算
BurnDownDataRows() が現在レコードと履歴レコードを UNION で返す理由は、C# 側でバーンダウンチャートを計算するためです。
// BurnDown クラスがデータを処理してチャートデータを生成
var burnDown = new Libraries.ViewModes.BurnDown(
context: context,
ss: ss,
dataRows: dataRows); // UNION された現在 + 履歴のレコード
BurnDown クラスは UNION 結果の Ver(バージョン)と UpdatedTime を使い、各日付時点での WorkValue の残量を計算します。これがバーンダウンチャートの縦軸(残作業量)の折れ線グラフを構成します。
| データ | 役割 |
|---|---|
| 現在レコード(Normal) | 現在の WorkValue・ProgressRate・Status |
| 履歴レコード(History) | 各バージョン更新時点での WorkValue の変化 |
| Ver / UpdatedTime | バーンダウン計算の時間軸 |
SELECT 列の内訳
バーンダウンチャートが取得する列を一覧にします。
| 列 | 説明 |
|---|---|
IssueId |
レコードの識別子 |
Ver |
バージョン番号(バーンダウン計算の時系列に使用) |
Title |
バーンダウン詳細ポップアップに表示するタイトル |
WorkValue |
作業量(バーンダウンの主軸値) |
StartTime |
開始日時(チャートの横軸範囲に影響) |
CompletionTime |
終了日時(チャートの横軸範囲に影響) |
ProgressRate |
進捗率(詳細表示用) |
Status |
ステータス(完了・未完了の判定) |
Updator |
更新者(詳細表示用) |
CreatedTime |
作成日時 |
UpdatedTime |
更新日時(バーンダウン計算の時間軸) |
バーンダウンチャート vs 他ビューの SQL 比較
| 項目 | 一覧 | カンバン | バーンダウンチャート |
|---|---|---|---|
| 件数チェック | なし(ページ分割) | InRange(SELECT COUNT) | InRange(SELECT COUNT) |
| データ SQL 本数 | 2 本(データ + COUNT) | 1 本 | 2 本(UNION) |
| テーブル | Normal のみ | Normal のみ | Normal + History(UNION) |
| ORDER BY | UpdatedTime DESC | なし | IssueId ASC, Ver ASC |
| GROUP BY | なし | なし | なし |
| C# での後処理 | なし | グループ化・集計 | 時系列バーンダウン計算 |
バーンダウンチャートは現在レコードだけでなくすべての履歴レコードを取得します。更新回数が多いレコードや件数が多い場合、取得データが非常に大きくなることがあります。BurnDownLimit で件数を制限しているのはそのためです。
まとめ
本記事では、バーンダウンチャートの SQL を解説しました。
-
InRange を使った件数チェックは、カンバンと同じ
SELECT COUNT方式 -
BurnDownDataRows() は現在テーブルと履歴テーブルを
UNIONで結合し、時系列バーンダウン計算に必要なすべてのバージョン情報を取得する - C# 側の
BurnDownクラスが UNION 結果を処理して、各日付時点の残作業量を計算する
第 7 回では、時系列チャートの HorizontalAxis による SQL パターン切り替えを解説します。