【ワレコのC#】LINQでグループ化してソートして数をカウントし集計する

この記事は約13分で読めます。
スポンサーリンク

このところ、久しぶりにC#を使ってプログラミングをしている。

EntityFrameworkを使ってデータベースを操作するプログラムだ。

ワテがデータベースを使えるようになったのは、2、3年前なので、まだあまり詳しくはない。

でも、最近では、EntityFrameworkのコードファーストなどを駆使して、自分のやりたいDB操作を自力で出来るようになった。

従来は、SQLコマンドを文字列で作成して、生のクエリーを実行していたのだが、エンティティフレームワークを使うとそんな必要は無い。

LINQの組み合わせでDBを操作出来るのだ。もちろん必要が有れば生クエリも実行出来る。

ワテがデータベースを習得する前は、データベースと言うとオラクルとかMySQLとか言う名前は知っていた。

そう言うデータベースソフトをサーバーコンピューターにインストールしなくてはデータベースは使えないので、何だか大掛かりな印象を持っていた。

なので、データベース自体には興味はあったのだが中々手が出なかった。

ところがレンタルサーバー(Windows VPS)を契約してそこにSQL Server Express 2016とかMySQLなどのデータベースソフトを入れて使ってみるととってもいい感じ。

食わず嫌いと言う感じだったのだ。

ワテでもやれば出来るやん。

当記事では、LINQの小技を紹介したい。

まあ、ワテの備忘録みたいなもんだが。

スポンサーリンク
ワテ推薦のプログラミングスクールで学ぶ
スポンサーリンク
スポンサーリンク

データベースと言うのは巨大な配列変数みたいなもん

LINQの小技を紹介する前にデータベースに対するワテの印象を紹介したい。

ワテの印象では、つまりまあ、データベースと言うのは、巨大な配列変数みたいなもんだと思えば良いだろう。配列変数と言うよりもリストと言うほうが良いかな。

ただしメモリ上にある普通の変数ではなくて、他のサーバー上にある感じ。

その巨大なリストに自分の好きなデータを書いたり読んだり。

なので、データベース使った事が無い人でも、配列変数やリスト変数を使ってプログラミングを出来る人なら、データベース使う事は非常に簡単だろう。

巨大な配列あるいはリストだと思えば良いからだ。

もちろん、データベースを使う為にはデータベースに接続するための一連の設定が必要になる。

具体的には、生クエリを実行する場合には、

  • ユーザーID
  • パスワード
  • 接続文字列(コネクションストリング)の作成
  • データベースの作成
  • テーブルの作成
  • クエリコマンドの実行
  • クエリ結果の取得

などだ。

EntityFrameworkのコードファーストの場合なら、

  • ユーザーID
  • パスワード
  • クラスの定義
  • LINQでDBを操作

とういう感じで、とっても簡単。

データベースもテーブルも全自動で生成される。

あとからクラスのメンバ変数を追加・削除・改名などの変更をすると、それはテーブルカラムの追加・削除などの変更に相当するが、DatabaseのMigrationコマンドを実行すれば、それらの変更が自動で反映されるし。

 

当初、生クエリを発行していた時には、データベースの操作と言うのはなんだかややこしいなあと思っていたのだが、EntityFrameworkを使い始めたらデータベースは配列みたいなもんだなあと言う印象を持つ様になった。

複数のプロセスから同時にアクセスしても、排他制御はデータベースがやってくれるし、必要なら自分でやっても良いし。

と言う事で、データベース未経験の人は、兎に角、使ってみる事をお勧めする。

でかい配列を扱っていると思えば簡単なもんだ。

さて、前置きが長くなったが、LINQの小技を紹介したい。

LINQでデータをグループ化しソートする

例えばこんなデータが有るとする。

 
public class 会社クラス
{
	public string 都道府県 { get; set; }
	public string 市区町村 { get; set; }
	public string 会社名 { get; set; }
}

public static List<会社クラス> Get会社クラスリスト()
{
	List<会社クラス> 会社クラスリスト = new List<会社クラス>
	{
		new 会社クラス {都道府県="東京都", 市区町村="目黒区" , 会社名="ワレコ商事"},
		new 会社クラス {都道府県="東京都", 市区町村="新宿区" , 会社名="ワレコ工務店"},
		new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="株式会社ワレコ"},
		new 会社クラス {都道府県="大阪府", 市区町村="西成区" , 会社名="ワレコ産業"},
		new 会社クラス {都道府県="東京都", 市区町村="目黒区" , 会社名="ワレコ企画"},
		new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ワレコ興行"},
		new 会社クラス {都道府県="大阪府", 市区町村="西成区" , 会社名="ワレコ組"},
		new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ワレクロ"},
		new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ZOZO Wareko"},
		new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ワレ天"},
	};

	return 会社クラスリスト;
}

つまりまあ、データベースに保管している会社のデータだ。

都道府県 市区町村 会社名

の三つの文字列型のカラムがある。現実の場面では、さらに何丁目何番何号とか、電話番号とか従業員数とか、いろんなカラムがあると思うが、この例では単純化している。

ちなみにワテの場合は半角カタカナ文字が大好きだ。

なので変数にも使うし、ワードその他の文書にも良く使う。

市区町村にある会社の数を集計したい

例えばこのデータに対して、目黒区にある会社の数は何社あるのかな?

と言う場合、どうやれば良いのか。

それが今回紹介するやり方だ。

クエリ構文LINQ版

List<会社クラス> 会社クラスリスト = Get会社クラスリスト();

var 会社Query =
	from 会社 in 会社クラスリスト

	// group 会社 by 会社.都道府県  into g	// 一つのキー(都道府県)でgroup化するならこれで良い。
	group 会社 by new                       // 複数のキーでgroup化する場合。
	{
		会社.都道府県,
		会社.市区町村,
	} into g
	orderby g.Key.市区町村 descending
	orderby g.Key.都道府県
	//o select g;	// Count()計算しないならこれで良い。
	select new
	{
		g.Key.都道府県,
		g.Key.市区町村,
		count = g.Count()
	};


var queryResult = 会社Query.ToList();

foreach (var 会社 in queryResult)
{
	Console.WriteLine("   {0}, {1}: {2}社", 会社.都道府県, 会社.市区町村, 会社.count);
}

コード:会社の数を集計するC#ソースコード(クエリ構文LINQ版)

 

その実行結果は以下の通り。

大阪府, 大阪市: 5社
大阪府, 西成区: 2社
東京都, 目黒区: 2社
東京都, 新宿区: 1社

上で紹介したLINQは select, where, group by などをヅラヅラと書いていくSQL文のような記述方法だ。

クエリ構文LINQと言うらしい。

その辺りの説明はこの記事にも書いてある。

【ワレコのC#】LINQのクエリ構文とメソッド構文(ラムダ式LINQ)【解決】
ワテの場合、昔はラムダ式とかLINQとかの記述方法は馴染めなかった。例えばC#のLINQはこんなやつだ。List&lt;int&gt; intLst = new List&lt;int&gt;() { 1, 2, 3, 6, 4, 2, 7

メソッド構文LINQ版

一方、Select().Where. みたいにメソッドをドットで連結する構文もある。

文字通りメソッド構文LINQと言うらしい。

上のクエリ構文LINQをメソッド構文LINQで書いてみた。

var 会社Query2 = 会社クラスリスト
	.GroupBy(g => new { g.都道府県, g.市区町村 })   // 都道府県名でグループ化トして、次に市区町村名でグループ化
	.OrderBy(o => o.Key.都道府県)                   // 都道府県名でソート
	.ThenBy(t => t.Key.市区町村)                    // その次は市区町村名でソート
	.Select(d => new
	{
		都道府県 = d.Key.都道府県,
		市区町村 = d.Key.市区町村,
		count = d.Count()
	})
	.ToList();

for (int i = 0; i < 会社Query2.Count; i++)
{
	var student_i = 会社Query2[i];
	Console.WriteLine("   {0}, {1}: {2}社", student_i.都道府県, student_i.市区町村, student_i.count);
}

コード:会社の数を集計するC#ソースコード(メソッド構文LINQ版)

その実行結果は以下の通り。

大阪府, 西成区: 2社
大阪府, 大阪市: 5社
東京都, 新宿区: 1社
東京都, 目黒区: 2社

最初の例と比較すると、集計した会社の数は同じだが、並びが異なる。

その理由は、最初の例では市区町村でのorderbyにdescending(降順)を入れているが、後者の例では入れていないからだ。

 

今回作成したC#の全ソースコード

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
	class Program
	{

		public class 会社クラス
		{
			public string 都道府県 { get; set; }
			public string 市区町村 { get; set; }
			public string 会社名 { get; set; }
		}

		public static List<会社クラス> Get会社クラスリスト()
		{
			List<会社クラス> 会社クラスリスト = new List<会社クラス>
				{
				   new 会社クラス {都道府県="東京都", 市区町村="目黒区" , 会社名="ワレコ商事"},
				   new 会社クラス {都道府県="東京都", 市区町村="新宿区" , 会社名="ワレコ工務店"},
				   new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="株式会社ワレコ"},
				   new 会社クラス {都道府県="大阪府", 市区町村="西成区" , 会社名="ワレコ産業"},
				   new 会社クラス {都道府県="東京都", 市区町村="目黒区" , 会社名="ワレコ企画"},
				   new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ワレコ興行"},
				   new 会社クラス {都道府県="大阪府", 市区町村="西成区" , 会社名="ワレコ組"},
				   new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ワレクロ"},
				   new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ZOZO Wareko"},
				   new 会社クラス {都道府県="大阪府", 市区町村="大阪市" , 会社名="ワレ天"},
				};

			return 会社クラスリスト;
		}

		static void Main(string[] args)
		{

			List<会社クラス> 会社クラスリスト = Get会社クラスリスト();

			var 会社Query =
				from 会社 in 会社クラスリスト

				//o group 会社 by 会社.都道府県  into g		// 一つのキー(都道府県)でgroup化するならこれで良い。
				group 会社 by new							// 複数のキーでgroup化する場合。
				{
					会社.都道府県,
					会社.市区町村,
				} into g
				orderby g.Key.市区町村 descending
				orderby g.Key.都道府県
				//o select g;								// Count()計算しないならこれで良い。
				select new
				{
					g.Key.都道府県,
					g.Key.市区町村,
					count = g.Count()
				};


			var queryResult = 会社Query.ToList();

			foreach (var 会社 in queryResult)
			{
				Console.WriteLine("   {0}, {1}: {2}社", 会社.都道府県, 会社.市区町村, 会社.count);
			}

			Console.WriteLine("--------------------");

			var 会社Query2 = 会社クラスリスト
				.GroupBy(g => new { g.都道府県, g.市区町村 })   // 都道府県名でグループ化トして、次に市区町村名でグループ化
				.OrderBy(o => o.Key.都道府県)                   // 都道府県名でソート
				.ThenBy(t => t.Key.市区町村)                    // その次は市区町村名でソート
				.Select(d => new
				{
					都道府県 = d.Key.都道府県,
					市区町村 = d.Key.市区町村,
					count = d.Count()
				})
				.ToList();

			for (int i = 0; i < 会社Query2.Count; i++)
			{
				var student_i = 会社Query2[i];
				Console.WriteLine("   {0}, {1}: {2}社", student_i.都道府県, student_i.市区町村, student_i.count);
			}


		}

	}
}

コード:会社の数を集計するC#の全ソースコード(コンソールアプリ)

その実行結果

大阪府, 大阪市: 5社
大阪府, 西成区: 2社
東京都, 目黒区: 2社
東京都, 新宿区: 1社
--------------------
大阪府, 西成区: 2社
大阪府, 大阪市: 5社
東京都, 新宿区: 1社
東京都, 目黒区: 2社
続行するには何かキーを押してください . . .

 

まとめ

自称、C#プログラミングの初心者のワテであるが、数カ月ぶりにC#のプログラミングを行っている。

最近ではLINQが大好きなので、forループなんかはあまり使わない。

その代わりにLINQやラムダ式を駆使して、本来なら複数行に渡る処理を一行で書く事に快感を覚えている。

そんな事をして何かメリットはあるのか?

多分、無い。

まさに、アホやがな。

まあ兎に角、目的のプログラムを短期間で完成させる事が最も重要だ。

乞うご期待。

LINQ関連商品

LinQ 新春特別公演 ~楽詣~(たのしもうで)あけましておめでとうございマ・シ・テ (Blu-ray)

ワテ推薦の独習C#だ。

スポンサーリンク
ワテ推薦のプログラミングスクールで学ぶ
コメント募集

この記事に関して何か質問とか補足など有りましたら、このページ下部にあるコメント欄からお知らせ下さい。

C#
スポンサーリンク
warekoをフォローする
スポンサーリンク
われこ われこ

コメント