はじめに:「重複カウント」は“どれがどれだけ被っているか”を一発で見抜く技
業務でデータを扱っていると、こういうことを知りたくなる場面がよく出てきます。
どの商品が何回注文されているか
どのユーザーが何回ログインしているか
どのエラーコードが何回発生しているか
つまり、「同じ値がいくつあるか=重複カウント」です。
C# の LINQ を使うと、この「重複カウント」をとてもシンプルに書けます。
ここでは、初心者向けに
重複カウントの基本パターン(GroupBy + Count)
「重複しているものだけ」を取り出す方法
オブジェクトの特定プロパティで重複カウントする方法
業務での具体例と、ユーティリティ化のアイデア
を、例題付きでかみ砕いて説明します。
基本形:GroupBy + Count で「値ごとの件数」を出す
まずは単純な文字列の重複カウント
using System;
using System.Linq;
var names = new[]
{
"Alice",
"Bob",
"Alice",
"Charlie",
"Bob",
"Alice"
};
var grouped = names
.GroupBy(x => x)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});
foreach (var x in grouped)
{
Console.WriteLine($"{x.Name}: {x.Count}");
}
C#出力はこうなります。
Alice: 3
Bob: 2
Charlie: 1
ここでの重要ポイントは、「GroupBy(x => x) で“同じ値ごとのグループ”を作り、g.Count() で“その値が何回出てきたか”を数えている」ということです。GroupBy の Key が「グループの代表値」、Count() が「その値の出現回数」です。
「重複しているものだけ」を取り出す
Count > 1 で“1 回以上ではなく、2 回以上”に絞る
「全部の件数」ではなく、「重複しているものだけ知りたい」ことも多いです。
その場合は、Count > 1 でフィルタします。
var duplicates = names
.GroupBy(x => x)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
})
.Where(x => x.Count > 1);
foreach (var x in duplicates)
{
Console.WriteLine($"{x.Name}: {x.Count}");
}
C#出力はこうなります。
Alice: 3
Bob: 2
Charlie は 1 回しか出ていないので除外されています。
ここでの重要ポイントは、「“重複”とは“2 回以上出ているもの”だと定義して、Count > 1 で表現する」ということです。
この条件を変えれば、「3 回以上出ているもの」なども簡単に書けます。
オブジェクトの特定プロパティで重複カウントする
例:ユーザーのメールアドレス重複チェック
public class User
{
public int Id { get; set; }
public string Email { get; set; } = "";
}
var users = new[]
{
new User { Id = 1, Email = "a@example.com" },
new User { Id = 2, Email = "b@example.com" },
new User { Id = 3, Email = "a@example.com" },
new User { Id = 4, Email = "c@example.com" },
new User { Id = 5, Email = "b@example.com" }
};
C#「同じメールアドレスが何人に使われているか」を知りたいときは、Email をキーにして GroupBy します。
var emailCounts = users
.GroupBy(u => u.Email)
.Select(g => new
{
Email = g.Key,
Count = g.Count()
});
foreach (var x in emailCounts)
{
Console.WriteLine($"{x.Email}: {x.Count}");
}
C#出力はこうなります。
a@example.com: 2
b@example.com: 2
c@example.com: 1
「重複しているメールアドレスだけ知りたい」なら、さきほどと同じように Count > 1 で絞ります。
var duplicatedEmails = emailCounts
.Where(x => x.Count > 1);
foreach (var x in duplicatedEmails)
{
Console.WriteLine($"Duplicated: {x.Email} ({x.Count})");
}
C#ここでの重要ポイントは、「GroupBy(u => u.Email) の“キー”を変えるだけで、どの項目で重複カウントするかを自由に切り替えられる」ということです。
商品コード、顧客 ID、日付、ステータスなど、業務で意味のある項目をキーにできます。
「重複しているレコードそのもの」を取り出す
GroupBy の中身(グループ)をそのまま使う
「どのメールアドレスが何回出ているか」だけでなく、
「そのメールアドレスを持つユーザーの一覧」も欲しいことがあります。
var duplicatedGroups = users
.GroupBy(u => u.Email)
.Where(g => g.Count() > 1);
foreach (var g in duplicatedGroups)
{
Console.WriteLine($"Email: {g.Key}");
foreach (var u in g)
{
Console.WriteLine($" Id={u.Id}, Email={u.Email}");
}
}
C#出力はこうなります。
Email: a@example.com
Id=1, Email=a@example.com
Id=3, Email=a@example.com
Email: b@example.com
Id=2, Email=b@example.com
Id=5, Email=b@example.com
ここでの重要ポイントは、「GroupBy の結果は“キー+そのキーに属する要素の列”なので、g の中身をそのまま列挙できる」ということです。
「重複しているグループごとに、その中身を全部見る」という処理が簡単に書けます。
実務での具体例 1:ログのエラーコード別発生回数
どのエラーがどれだけ出ているかを集計する
public class LogEntry
{
public DateTime Time { get; set; }
public string Level { get; set; } = "";
public string ErrorCode { get; set; } = "";
}
var logs = new[]
{
new LogEntry { Time = DateTime.Now, Level = "Error", ErrorCode = "E001" },
new LogEntry { Time = DateTime.Now, Level = "Error", ErrorCode = "E002" },
new LogEntry { Time = DateTime.Now, Level = "Error", ErrorCode = "E001" },
new LogEntry { Time = DateTime.Now, Level = "Warn", ErrorCode = "W001" },
new LogEntry { Time = DateTime.Now, Level = "Error", ErrorCode = "E001" },
};
C#var errorCounts = logs
.Where(x => x.Level == "Error")
.GroupBy(x => x.ErrorCode)
.Select(g => new
{
ErrorCode = g.Key,
Count = g.Count()
})
.OrderByDescending(x => x.Count);
foreach (var x in errorCounts)
{
Console.WriteLine($"{x.ErrorCode}: {x.Count}");
}
C#ここでの重要ポイントは、「Where で対象を絞ってから GroupBy する」という流れです。
「Error レベルだけ」「特定期間だけ」など、業務条件を先にかけてから重複カウントします。
実務での具体例 2:注文ごとの商品別数量集計
商品コードごとに「何回注文されたか」を数える
public class OrderLine
{
public string ItemCode { get; set; } = "";
public int Quantity { get; set; }
}
var lines = new[]
{
new OrderLine { ItemCode = "A", Quantity = 1 },
new OrderLine { ItemCode = "B", Quantity = 2 },
new OrderLine { ItemCode = "A", Quantity = 3 },
new OrderLine { ItemCode = "C", Quantity = 1 },
new OrderLine { ItemCode = "B", Quantity = 1 },
};
C#「何回注文に登場したか(行数ベース)」を数えるなら、単純に Count() です。
var itemCounts = lines
.GroupBy(x => x.ItemCode)
.Select(g => new
{
ItemCode = g.Key,
Count = g.Count()
});
C#「合計数量」を知りたいなら、Count() ではなく Sum(x => x.Quantity) を使います。
var itemQuantities = lines
.GroupBy(x => x.ItemCode)
.Select(g => new
{
ItemCode = g.Key,
TotalQuantity = g.Sum(x => x.Quantity)
});
C#ここでの重要ポイントは、「GroupBy のあとに何を集計するかは自由」ということです。Count() は「行数」、Sum は「数量」、Max や Min も同じパターンで書けます。
重複カウントをユーティリティ化する
「CountBy」的な拡張メソッドを作る
毎回 GroupBy(...).Select(...) と書くのが長いと感じたら、
「重複カウント専用」の拡張メソッドを 1 本用意しておくと便利です。
using System.Collections.Generic;
using System.Linq;
public static class CountByExtensions
{
public static IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(
this IEnumerable<TSource> source,
System.Func<TSource, TKey> keySelector)
{
return source
.GroupBy(keySelector)
.Select(g => (Key: g.Key, Count: g.Count()));
}
}
C#使い方はこうです。
var counts = names.CountBy(x => x);
foreach (var x in counts)
{
Console.WriteLine($"{x.Key}: {x.Count}");
}
C#ユーザーのメールアドレスならこうです。
var emailCounts = users.CountBy(u => u.Email);
C#ここでの重要ポイントは、「CountBy のような名前にすることで、“ここで重複カウントしている”ことが一目で分かる」ということです。GroupBy + Count のパターンを、チーム共通のユーティリティとしてしまうイメージです。
まとめ:「重複カウントユーティリティ」は“偏りや異常を一瞬であぶり出す道具”
重複カウントの本質は、
同じ値がどれだけ出ているかを数えることで、
データの偏りや異常、重複登録などを一瞬で見抜く
ことです。
押さえておきたいポイントを整理すると、
基本形は GroupBy(key).Select(g => new { Key = g.Key, Count = g.Count() })
「重複だけ」は Count > 1 で絞る
オブジェクトの特定プロパティで重複カウントするには、GroupBy(u => u.Email) のようにキーを変える
グループの中身をそのまま列挙すれば、「重複しているレコードそのもの」も取れるCountBy のような拡張メソッドにしておくと、業務コードがかなり読みやすくなる
ここまで腹落ちしていれば、
「なんとなく for で辞書を回して数える」段階から卒業して、
“LINQ でサッと重複カウントして、データの状態を一瞬で把握する”ことができるようになります。
