C# Tips | コレクション・LINQ:グルーピング

C# C#
スポンサーリンク

はじめに:「グルーピング」は“バラバラの明細を、かたまりにまとめる技”

業務システムでは、こんなことをよくやります。

「部署ごとに社員をまとめたい」
「顧客ごとに売上を集計したい」
「日付ごとにアクセス数を集計したい」

この「同じキーを持つデータを、ひとまとまりにする」のが、グルーピングです。
C# の LINQ では GroupBy を使うことで、これをとても自然に書けます。

ここでは、初心者向けに

  • GroupBy の基本
  • グループごとの集計(件数・合計・平均など)
  • 実務でよくある「顧客別売上」「部署別社員一覧」
  • グルーピング結果をユーティリティとして扱うコツ

まで、順番にかみ砕いて説明していきます。


GroupBy の基本:同じキーを持つものを「かたまり」にする

まずはシンプルな例で動きをつかむ

数値を「偶数グループ」と「奇数グループ」に分けてみます。

using System;
using System.Collections.Generic;
using System.Linq;

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

var groups = numbers.GroupBy(x => x % 2 == 0 ? "Even" : "Odd");

foreach (var g in groups)
{
    Console.WriteLine($"Key = {g.Key}");

    foreach (var n in g)
    {
        Console.WriteLine($"  {n}");
    }
}
C#

出力イメージはこんな感じです。

Key = Odd
  1
  3
  5
Key = Even
  2
  4
  6

ここでの重要ポイントは、「GroupBy の結果は“グループの集合”」だということです。
各グループは「Key(グループのキー)」と「そのキーに属する要素の列」を持っています。

g.Key がグループの名前(ここでは “Odd” / “Even”)、
g 自体が「そのグループに属する要素の列」です。


オブジェクトを業務キーでグルーピングする

社員を「部署ごと」にまとめる

業務で一番よくあるのは、「部署ごと」「顧客ごと」「日付ごと」といったグルーピングです。

社員クラスを用意します。

public class Employee
{
    public int No { get; set; }
    public string Name { get; set; } = "";
    public string Department { get; set; } = ""; // "Sales", "Dev", ...
}
C#

データを作ります。

var employees = new List<Employee>
{
    new Employee { No = 1, Name = "Sato",   Department = "Sales" },
    new Employee { No = 2, Name = "Suzuki", Department = "Dev" },
    new Employee { No = 3, Name = "Tanaka", Department = "Sales" },
    new Employee { No = 4, Name = "Yamada", Department = "Dev" },
};
C#

部署ごとにグルーピングしてみます。

var groups = employees.GroupBy(e => e.Department);

foreach (var g in groups)
{
    Console.WriteLine($"Department = {g.Key}");

    foreach (var e in g)
    {
        Console.WriteLine($"  {e.No}: {e.Name}");
    }
}
C#

出力イメージはこうです。

Department = Sales
  1: Sato
  3: Tanaka
Department = Dev
  2: Suzuki
  4: Yamada

ここでの重要ポイントは、「GroupBy(e => e.Department) で“部署ごとに社員をまとめる”」という意味になることです。
g.Key が部署コード、g の中身がその部署の社員一覧です。


グループごとの集計:件数・合計・平均など

部署ごとの社員数を出す

グルーピングの次の一歩は、「グループごとに集計する」ことです。
例えば「部署ごとの社員数」を出してみます。

var result = employees
    .GroupBy(e => e.Department)
    .Select(g => new
    {
        Department = g.Key,
        Count      = g.Count()
    });

foreach (var x in result)
{
    Console.WriteLine($"{x.Department}: {x.Count} 人");
}
C#

ここでの重要ポイントは、「GroupBy のあとに Select で“グループ単位の結果”を作る」ことです。
g.Count() は「そのグループに属する要素の件数」を返します。

顧客ごとの売上合計を出す

もう少し実務っぽい例として、「顧客ごとの売上合計」を考えてみます。

public class Sale
{
    public string Customer { get; set; } = "";
    public DateTime Date { get; set; }
    public int Amount { get; set; }
}
C#

データを用意します。

var sales = new List<Sale>
{
    new Sale { Customer = "A", Date = new DateTime(2026, 2, 10), Amount = 1000 },
    new Sale { Customer = "B", Date = new DateTime(2026, 2, 10), Amount = 2000 },
    new Sale { Customer = "A", Date = new DateTime(2026, 2, 11), Amount = 3000 },
};
C#

顧客ごとの合計金額を出します。

var summary = sales
    .GroupBy(s => s.Customer)
    .Select(g => new
    {
        Customer = g.Key,
        Total    = g.Sum(x => x.Amount),
        Count    = g.Count()
    });

foreach (var x in summary)
{
    Console.WriteLine($"{x.Customer}: 合計 {x.Total} 円 / {x.Count} 件");
}
C#

ここでの重要ポイントは、「g.Sum(x => x.Amount) のように、グループ内の要素に対してさらに LINQ を使える」ことです。
Count, Sum, Average, Min, Max など、集計系メソッドはグルーピングと相性抜群です。


日付ごとのグルーピング:日別集計の定番パターン

DateTime を「日付だけ」でグルーピングする

売上やアクセスログなどでは、「日付ごとの件数・合計」を出したいことが多いです。

var daily = sales
    .GroupBy(s => s.Date.Date) // 時刻を切り捨てて“日付だけ”でグルーピング
    .Select(g => new
    {
        Date  = g.Key,
        Total = g.Sum(x => x.Amount)
    })
    .OrderBy(x => x.Date);
C#

DateTime.Date は「時刻部分を 00:00:00 にした日付」を返すので、
同じ日付のデータが同じグループにまとまります。

ここでの重要ポイントは、「グルーピングのキーを“どう切り出すか”が設計の肝」だということです。
日付単位なら s.Date.Date、月単位なら new { s.Date.Year, s.Date.Month } のように、
“まとめたい単位”をキーとして渡します。


複合キーでグルーピングする

「顧客 × 年月」ごとの売上集計

例えば、「顧客ごと・月ごとの売上」を出したいとします。
このときのキーは「顧客」と「年月」の組み合わせです。

var monthly = sales
    .GroupBy(s => new { s.Customer, s.Date.Year, s.Date.Month })
    .Select(g => new
    {
        g.Key.Customer,
        g.Key.Year,
        g.Key.Month,
        Total = g.Sum(x => x.Amount)
    })
    .OrderBy(x => x.Customer)
    .ThenBy(x => x.Year)
    .ThenBy(x => x.Month);
C#

ここでの重要ポイントは、「匿名型 new { ... } をキーに使うと、複数項目をまとめてグルーピングできる」ことです。
g.Key の中に、Customer, Year, Month が全部入っています。


グルーピング結果を“辞書っぽく”扱う

ToDictionary で「キー → グループ」を引けるようにする

「部署コードを指定したら、その部署の社員一覧を取りたい」というようなとき、
グルーピング結果を Dictionary にしておくと便利です。

var map = employees
    .GroupBy(e => e.Department)
    .ToDictionary(g => g.Key, g => g.ToList());
C#

これで、map["Sales"] で「営業部の社員一覧」が取れるようになります。

if (map.TryGetValue("Sales", out var salesEmployees))
{
    foreach (var e in salesEmployees)
    {
        Console.WriteLine($"{e.No}: {e.Name}");
    }
}
C#

ここでの重要ポイントは、「GroupByToDictionary で“キーからグループを引けるマップ”を作れる」ことです。
頻繁に参照する場合は、この形にしておくとアクセスが楽になります。


実務で意識してほしいこと

「何でまとめたいのか」を日本語で先に決める

グルーピングを書く前に、必ず言葉で整理してください。

部署ごとに社員をまとめたい
顧客ごとに売上をまとめたい
日付ごとにアクセス数を集計したい
顧客 × 年月ごとに売上を集計したい

これが決まれば、GroupBy のキーはほぼ自動的に決まります。

部署ごと → GroupBy(e => e.Department)
顧客ごと → GroupBy(s => s.Customer)
日付ごと → GroupBy(s => s.Date.Date)
顧客 × 年月 → GroupBy(s => new { s.Customer, s.Date.Year, s.Date.Month })

という具合です。

「グルーピング → 集計 → ソート」の流れを意識する

一覧やレポートでは、だいたいこの流れになります。

グルーピングする(GroupBy)
グループごとの集計結果を作る(Select)
表示しやすい順に並べる(OrderBy / ThenBy)

順番を意識して書くと、コードの意図がとても読みやすくなります。


まとめ:「グルーピングユーティリティ」は“明細を意味のあるかたまりに変える道具”

グルーピングの本質は、

「バラバラの明細を、“同じ意味を持つもの同士”でまとめて、
グループ単位で考えられるようにする」

ことです。

押さえておきたいポイントは、

GroupBy(キー) で「キーごとのグループ」を作る
グループごとの集計は Select(g => new { g.Key, ... g.Count(), g.Sum(...) ... }) の形で書く
複合キーは new { ... } を使う
GroupByToDictionary で「キー → グループ」のマップも作れる

ここまで身につけば、「1件ずつ for で回して自前で集計する」スタイルから卒業して、
“LINQ らしい、読みやすいグルーピング&集計”を業務コードに自然に組み込めるようになります。

タイトルとURLをコピーしました