0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プリザンター内部で動くSQL文を調べてみる[第6回:バーンダウンチャート編]

0
Posted at

はじめに

第 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() を呼び出します。

Models/Issues/IssueUtilities.cs
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 で結合して返します。

Models/Issues/IssueUtilities.cs
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# 側でバーンダウンチャートを計算するためです。

Libraries/ViewModes/BurnDown.cs
// 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 パターン切り替えを解説します。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?