ページ

2011年4月29日金曜日

◆横浜、横綱相撲。

4月29日横浜線。

1回表、坂本がいきなり2塁打で出塁。
続く脇谷、バントをファールした挙句空振りの三振。
全く持って何を考えているのか。
絶対にやっちゃいけないのが三振ってのは中学生でも判ること。
これがクリーンアップとかならまだ話は分かる。
もしくは相手が150K以上の球を投げるすごいピッチャーだとか。
本当にダメダメだ。

試合結果は2流投手同士の投げ合いで打ち合いとなったが、しつこさで優った横浜の勝ち6-7 。

巨人は先発もダメだが、中継ぎの弱さも相変わらず。
今年はいくらかやるかと思われた西村も相変わらず。
球自体は悪くないのだがノーアウトから1本ヒットを打たれただけですぐ弱気になってコースばかり狙う。
カウントを悪くしてはポコン。
するともっと弱気になって四球。
指導者も悪いんだろう。

5-0になった時点で巨人だったらプリプリ行くところを、横浜は1点ずつしつこく取りに来る。
ちょっと詰め寄られると巨人はすぐにバタバタし始める。
ピッチャーは相変わらずだけど今年の横浜の打線は良いね。

ちなみにゴンちゃんは相変わらずだったな。

去年が調子が悪かったのではなく一昨年がたまたま良かったのだと早く気付かないと去年の2の舞。

まぁ、判ってても代わりも居ないし仕方ないか。

勝っても負けても良いが、もっとレベルの高い試合が見たいものだ。

2011年4月28日木曜日

◆館山、パーフェクト

なピッチング。

本当に素晴らしいピッチングだった。
ボテボテのラッキーヒット2本とデッドボール一つ。
内容的にはパーフェクトに等しい。

巨人の貧打線ではどうにもならない投手って感じ。

巨人の澤村も悪くなかった。
十分合格だろう。
今年の新人の中では一番ではなかろうか。
裕ちゃんよりはるかに良い。
巨人打線が相手だったら完封したのではないだろうか。

ヤクルト打線はやはりしぶとい。
青木、田中、宮本あたりは良いピッチャーが相手でも何とか食らいついていく。
右打ちが非常に多い。

それに引き替え、巨人打線。
相手ピッチャーが素晴らしく、自分たちの打線も調子が悪いのに、相も変わらずプリプリ引っ張ってばかり。
全く策がない。
そういう打線だと言ってしまえばそれまでだが、脇谷あたりはもう少し頭を使えないものだろうか。
どうも似たタイプのバッターばかり。
テレビを見る限りでは亀井あたりも復活しそうにないな。

小笠原もだいぶ深刻。
4打席4三振ではね・・・。
巨人で唯一粘りのあるバッターなのに。

ライアルは語るに値しない。
4打席4三振でも、当たればホームランの可能性ありって感じならまだしも、ありゃどう見てもうまくいってセンター前ゴロのヒットって感じだ。

ラミレス、小笠原の後釜を養成するために今年は若手に切り替えたらどうだろうか。

っといってもあまり楽しみな若手もいないんだよな・・・。

2011年4月27日水曜日

◆消化試合?

4月27日ヤクルト戦。

1対4で巨人の負け。

まるで消化試合を思わせるような覇気のない試合。
まぁ内海は実力的にあんなもんでしょう。5回までに3点ならごく普通。

ただし、今の巨人では3点取られた時点で勝負あった。
よっぽど3流のピッチャーじゃない限りは1,2点がやっとといった感じ。

ヤクルトのバレンティンは良さそうだね。
ヤクルトは比較的外人を取るのがうまい。

今年の巨人の外国人は総崩れ。

日本人もあまり魅力的な選手はいないなぁ。
坂本、長野あたりは悪くはないけど、確実性がない。
積極性はあるが、選球眼が悪い。
私は基本的に選球眼の悪い選手が好きではない。
選球眼が悪くて一流になった選手はあまり記憶がないし。
ただ、わき役としてみた場合には結構魅力がある。

なので、3番4番を打つ生きのよい日本人選手が出てきてほしいところだが、ファームをみてもまったく見当たらない。

脇谷あたりもそろそろ開眼しても良いころなのだが、相変わらず淡白なバッティングを繰り返す。
自分のタイプを判っていないのだろうか。
もっとひきつけて粘っこいバッティングしないと。

松本は何をやってるんだ?早く出てこい。
去年の救世主朝井は何してるんだ?
わが県期待の星、木村正太はまだけがが治らないの?もうだめ?

原は今年も外国人の調子を測りきれず悪くてもバカの一つ覚えで使い続けていくんだろうな。
監督になりたての頃は、へぇーと思わせる選手起用をしていたが最近では誰でもできる保守的な起用ばかり。

今シーズンの楽しみは澤村だけか・・・。

◆IISエラーページのカスタマイズ

エラーページをカスタマイズするにはIISマネージャからエラーページを編集すれば良いらしい。
image

image

デフォルトは"C:\Windows\System32\inetsrv\config\applicationHost.config"で設定されていているようだ。
image

これを見るとエラーページは"%SystemDrive%\inetpub\custerr"に置かれている。

先のエラーページ編集画面にてサイト固有のエラーページの設定が可能で、その設定値はサイトフォルダーのweb.configに置かれるって仕掛けなのだろう。

ただし、このエラーページ設定画面の使い方が今一つ良く解らない。
例えば、404をカスタマイズしようとしてその行をダブルクリックするとこんな画面が表示される。
image

ここでファイルパスを編集するために「設定」ボタンをクリック。
image

試しにcusterr2を指定してみる。(実体はcusterrからそのままコピーした)

しかし、なぜかこれではうまくいかない。
image

なんとなく類推するにapplicationHost.configの以下の部分でそういう指定がされているように見える。(単純に英語を解釈すると)
image

この指定を修正すれば良いのだろうが、applicationHost.configを触るのは気が引ける。Web.configでこの設定自体を上書きるのかもしれないが、あまり突き詰める問題でも無さそうなので素直に相対パスを指定することとした。
が、その相対パスの指定の仕方が判らない。
image

この画面の「ルートディレクトリパス」が物理パス以外上手く受け付けてくれない。
..\とかやってみても、エラーにはならないのだが、思った場所を見に行ってくれない。..\..\..\..\..\とかありえなそうな指定をしてもエラーにならないのでさっぱり判らない。

なぜ、こんな簡単そうな事がこんなにも難しいのか・・・。
あれこれ試しているうちにIISマネージャが異常終了までする始末。

ーー

一晩眠って気を取り直し。
仕方が無いので英語情報を漁って見る。
すると、どうも根本的に勘違いをしていたような気がしてきた。

image
この画面で、エラーページを単純に差し替えるには「静的ファイルのコンテンツをエラー応答に挿入」、aspxなどに置き換えるには「このサイトでURLを実行」を指定すると単純に字面から判断していたのだが、どうもこれが違っているような気がしてきた。

「静的ファイル」と「実行」というキーワードで脊髄反射的にそう解釈していた。

「エラー応答に挿入」に注目すると、テンプレート的なエラーページに対して部分的に挿入するという事なのではないだろうか。

「実行」という文字と「例:hoge/404.aspx」で、ここではaspxを指定するものだと思っていたが単純に差し替えたい場合はここにhtmlファイルをしてすれば良いのではなかろうか。

なおかつ、デフォルトでは以下の機能設定の編集にて、
image

image

ローカル要求に対してはカスタムエラーページを表示しないようになっているので、リモートからアクセスして確認するか、「カスタムエラーページ」に変更してから確認してみると良さそうだ。

これでやっと以下のうようなエラーページを表示することが出来た。
image

「静的ファイルのコンテンツをエラー応答に挿入」の具体的な方法は未だに解らないままだが(調べていない)、今回はIISの機能を俯瞰するのが目的なのでこのへんで良とする。

--

もう一度調べてみた
IIS: ◆エラーページをカスタマイズする

2011年4月26日火曜日

◆東野相変わらず

4月26日ヤクルト戦。

いつものように石川を打てず。
そろそろ強力打線ていう枕詞はやめてほしい。恥ずかしいから。

東野は良さそうに見えて、ピンチが来るとバタバタとやられる。
去年の後半からずーっと同じ。

まったくつまらない試合。
1-7で巨人のぼろ負け。

アルバラデホ、使えん。
ライアル出すくらいなら太田でも使った方が将来のためになる。

阿部が戻るまで5割程度なら御の字って状態だな。

◆IE9を入れてみた

入れて1分でバグに遭遇(笑)。

IE9入れると紹介ページが立ち上がるので、新機能とやらを試してみた。
表示したタブをWindows7のタスクバーにドロップするとピン止め出るのだとか。
っが、出来ない。
image

私のデスクトップは此の様にタスクバーの位置を右側にしている。(マルチウインドウにしているので、この右にもう一つ画面があり、このほうが使いやす)
どうもこれがよろしくないようだ。
ウインドウを画面の右端にドラッグしたときに自動的に右半分表示にしてくれる機能と干渉しているのだろうか・・・。

最近のMSときたら、と嘆いてみても始まらないので回避策を探る。
ドラッグ&ドロップ以外にもメニューとかでできそうなものなのだが、単純には見つからない。

ふと思いつき、アドレスバーにあるアイコンをドラッグ&ドロップしてみる。
image

こんどはOKだ。

それ以外は目に見えて便利になった機能は無さそう・・・。
一口で言えばGUIについてはChromeの良いところをパクリましたといった感じなので、Chromeを使ったことがある人は違和感なく使えるのではないだろうか。

よく言えば、Choromeの使いやすさ軽さとIEの互換性の高さが融合されたといった感じで特にこだわりのない私としては嬉しいバージョンアップだ。

XP向けには提供されないようなので、果たしてWindows7への移行を加速させるのか、XPユーザーのIE離れを加速させるのか興味深い。

2011年4月21日木曜日

◆澤村を初めて見る

4月21日阪神戦。

先発はルーキー澤村。
初めてテレビで見た。

全体的にレベルの高いピッチャーなのは確かなようだ。
球速もあるし、変化球も一通り投げる。コントロールも悪くはなさそう。
ピンチでもあまりバタバタしないので精神的にも強そうな印象を受けた。
5回に好調の鳥谷に対し、ストレートでファールを打たせ、フォークで三振を取ったあたりはなかなかのピッチング。
これからどの程度伸びていくのか楽しみだ。

それに引き替え打線は相変わらず。
毎年毎年左ピッチャーにはやられっぱなしだ。

それにしても今年の阪神はピッチャーが良いね。
これで打線が去年通りだったら100勝くらいしたかもね。
しかし、そこが野球の不思議なところで打線はさっぱり。
マートン、平野あたりは全く去年の面影が無い。
加えて金本あたりはさすがの鉄人もそろそろ衰えは否めない感じ。

どうなることやら。

後半へ続く・・・。

3対1で巨人が勝ったようだ。
6安打同士の投手戦?

澤村は堂々としたもんだね。
イメージ的には、まっすぐとフォークで三振かホームランかフォアボールかって感じのピッチャーかと思っていたのだがだいぶ違うようだ。
どちらかというとスライダーが武器って感じだなぁ。
もうある程度完成されたピッチャーだね。

今後が楽しみだ。

ただ、今年もピッチャー、バッターともに数が足りないな。
ロメロが抑えをやってたけど間違って大化するのを期待だな。

2011年4月20日水曜日

◆型推論

  static Z F<X, Y, Z>(X value, Func<X, Y> f1, Func<Y, Z> f2)
  {
    return f2(f1(value));
  }


ちょっと前までなら、これを見て「多分これはC#ではない」と思った可能性大である。
Zって何?Fって何?Funcって何?っと?だらけである。

stati int hogeGet(string value , hogeMethod f1 , pekeMethod f2)なんて書いてあればなんてことはないのだが、intがZに変わっただけで全く違った印象になってしまうものだ。
なれれば便利であろう型推論も慣れないと「なんだこれは」となってしまう。

まぁ、これは今に始まったことではなく、クラス名に続くコロンもわかっている人にとっては「継承、もしくは実装」と簡潔に理解できるがそうでない人にとっては、このコロンて何よ?となってしまうだろう。
(ちなみにコボラーな私は最初にC系の言語を見たときにvoidって何?とずっと思っていた)
これが、VBなんかだと若干緩和されていて継承はinherit、実装はimplementといったキーワードが使われるので普通の人は想像がつく。
良くも悪くもC#は簡潔な言語である。

話は戻って、上記のメソッドは第2回 ラムダ式と型推論 - @ITにてこんなサンプルとして載っていた。

using System;
class Program
{
  static Z F<X, Y, Z>(X value, Func<X, Y> f1, Func<Y, Z> f2)
  {
    return f2(f1(value));
  }
  static void Main(string[] args)
  {
    double seconds = F("1:15:30",
                       s => TimeSpan.Parse(s),
                       t => t.TotalSeconds);
    Console.WriteLine(seconds);
  }
}

使い方を見て、やっと
static double F<string,TimeSpan,double>~
って事かと判る。
すなわち、型推論の前にジェネリックに今ひとつついていけていないのだ。
このFメソッドの定義を見て「おー、これは便利なメソッドだ」といってその下のサンプルのような実装を私が書くことは無いように思う。(笑)

話を推論型に戻すと、上記サンプルは本来型指定を行えば、

class Program
{
    static Z F<X, Y, Z>(X value, Func<X, Y> f1, Func<Y, Z> f2)
    {
        return f2(f1(value));
    }
    static void Main(string[] args)
    {
        double seconds = F<string,TimeSpan,double>("1:15:30",
                           (string s) => TimeSpan.Parse(s),
                           (TimeSpan t) => t.TotalSeconds);
        Console.WriteLine(seconds);
    }
}

てな感じになるのだろうか。
慣れないとこの方が判り易いと思うが、慣れてくると冗長でタコなコーディングと批判されるのだろう。

COBOLの時代でも、あまりに頭のよい人が書いたソースはどうも判りづらいと思ったものがだが、最近のC#はよりソースの質の差が出やすくなってきているように思う。

自分一人で作って、自分一人がメンテナンスするのであれば何も問題は無いのだが、大プロジェクトになってくるとその基準を決めるのが難しい。
特に最近のプロジェクトでは作る人と保守する人が違うのは当たり前になってきているので余計考えさせられる。

COBOLの時代は良かったなぁ。(笑)

◆拡張メソッドの定義

拡張メソッドであるWhereは以下のようなLINQなどで使える。

using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
  static void Main(string[] args)
  {
    int[] n = { 2, 3, 5, 7, 11, 13 };
    IEnumerable<int> numQuery2 = n.Where(num => num < 10);
    foreach (int i in numQuery2)
    {
      Console.WriteLine(i);
// 出力:
// 2
// 3
// 5
// 7
    }
  }
}


まぁこのサンプル自体はLINQが少しでも判っていれば簡単に書けるだろう。
ただし、Where拡張メソッドの定義は以下のようになっているのだが、この定義を見て上記Whereの構文が書けるだろうか。

public static IEnumerable<TSource> Where<TSource>(
  this IEnumerable<TSource> source,
  Func<TSource, bool> predicate
)


はっきり言ってチンプンカンプンである。
拡張メソッドのの使い勝手自体は気に入っているがその仕組みはちょっと無理やりっぽい感じもある。
そもそもあるクラスのメソッドを他のクラスを使って拡張するというのは反則ではなかろうか。(笑)

まず戻り値はIEnumerable<TSource> 型。これは判る。
次に、引数はIEnumerable<TSource>型Func<TSource, bool> 型の2つを取る。そして最初の引数にはthisなんてよく判らないキーワードが付いている。
どうも、この2つの引数は上記サンプルで使用しているWhereメソッドの引数(num => num < 10)を単純にさすわけではなさそうだ。
結果から類推するに、Whereメソッドは最初の引数
 IEnumerable<TSource> 型に対する拡張ができるよという事ではなかろうか。(逆に言うとIEnumerable<TSource> 型でWhere拡張メソッドが使えるという事)
thisというキーワードは拡張メソッドだよってしるしかな。
2つ目の引数が実際に拡張メソッドとして使った時の引数。
ここではFunc<TSource, bool>デリゲートなので、TSourceを引数としてboolを返すメソッドまたは同等のラムダ式という事で
Where(num => num < 10) という使い方ができるという事ではなかろうか。
ちなみにwhere<TSource>の<TSource>は型推定(だっけ?)で推定できるので省略可能って事。

と解釈してみた。

◆ラムダ式サンプル

おなじみ@ITのサイトからラムダ式のサンプルを転載しておく。
LINQのために導入されたラムダ式だが、LINQ以外でも簡略化されたDelegateとして使い出があるようだ。

using System;
delegate int 引数なし();
delegate int 引数1つ(int onlyOne);
delegate int 引数2つ(int first, int second);
delegate void 戻り値なし();
class Program
{
  static void Main(string[] args)
  {
    引数なし sample1 = () => 0;
    引数1つ sample2 = (x) => x * 2;
    引数1つ sample3 = (int x) => x * 2;
    引数1つ sample4 = x => x * 2;
    引数2つ sample5 = (x, y) => x * y;
    引数2つ sample6 = (int x, int y) => x * y;
    引数1つ sample7 = (x) => { return x * 2; };
    引数1つ sample8 = (int x) => { return x * 2; };
    戻り値なし sample9 = () => Console.WriteLine("Hello!");
  }
}

◆新外国人トーレス先発

4月20日阪神戦。

全く活躍したためしのないのが巨人が連れてくる外国人。
クロマティ以来、何十年と失敗続き。
スカウトは目開いてみてるんだろうか。

やはり予想通り、3回まで早くも5安打3四球とメロメロ。

かたや阪神の外国人スタンリッジは素晴らしいピッチング。
今日もはや勝負あったって感じ。

と思っていたらひょんな所から巨人に流れがやってきて同点。
まずはめったに四球を取らないラミレスがフォアボール。
なんか、読みが外れてインサイドのカーブに手が出なかったって感じ。ストライクといわれても仕方がないくらいのボールだった。
続く高橋由伸は2ストライク1ボールからこれまたフォアボール。
良いピッチングで誘っていたが、由伸が手を出さなかった。調子が良いんだろう。
つづく長野はボテボテのラッキー内野安打。
そして今日初スタメンの亀井。
ラッキーなことに初球真ん中のストレートをヒットで2点。(それでも先っぽか詰まったのか会心の当たりではなかったが)
本当に野球は何が起きるかわからない。

トーレス、実際にテレビで見てみたが今ひとつ実力を測りきれなかった。
ストレートはそこそこ良さそう。変化球はスライダーとカーブか。チェンジアップも投げているのかもしれないがよく判らなかった。コントロールも良からず悪からず。未知数な分だけグライシンガーやゴンザレスよりも良いか。
とにかく阪神打線が当たっていないので判断しかねる。
今年の阪神はピッチャー次第かな。

ドラフト1位の榎田は良さげ。大化けするかも。

何にしても今日は巨人につきがあった。
微妙な判定が多く、だいぶ巨人に有利な流れになった気がする。

最後はロメロがなげて1点取られたようだが、今年も抑えがはっきりしなそうだ。
そういえば朝井とかどうしたんだ?
木村は今年も出てこれないのか?

5対4でついてた分だけ巨人の勝ち。

各チームとも今年は投高打低。というか去年が打ちすぎ。
和田とかマートンとか神がかっていた不思議な年だった。

今年は混戦になりそうだ。
中日打線が去年に輪を掛けてダメっぽいので今年も出遅れそう。
阪神も打線がダメなので抜け出るのは厳しそう。
ヤクルトも打線が弱い。(畠山がんばっているな)

◆能見今年もか

4月19日阪神戦。

相変わらず能見にやられる。
決め球のフォークに面白いように三振していく。
去年同様バントも全く決まらず。

東野のそこそこだったが去年同様、ピンチに弱い。精神的なものだろうか。
能見のようなフォークがあれば一流ピッチャーなんだけどね。
なんで決め球の習得に励まないんだろう。

阪神打線が去年のような迫力がないため良い勝負になったが、最後は新井(弟)のラッキーパンチでジ・エンド。

◆ゴンザレス今年もか

4月17日広島戦。

広島の先発はドラフト1位ルーキー福井。
初めて見たが、騒がれたドラフト1位にしては若干スケールが小さい感じ。
まっすぐはこれからもう少し出るのだろうか。
スライダーは普通より若干抜けた感じがして打者のタイミングが外れている。
フォークも投げていたが、まだ調子が出ていないのかな?
スライダーピッチャーって感じかなぁ。

相変わらず巨人は初物に弱く攻めあぐねる。
そこそこヒットは出ているのだが決め手に掛けるのは去年と一緒。

ゴンザレスも去年と同じ感じだなぁ。
一昨年がたまたま良かっただけなのかもしれないと思い始めてきた。

試合はこれといった盛り上がりもなく巨人の負け。

阿部が戻って小笠原の調子が上がるまではこんな感じが続きそう。

◆Excelでの和暦表示

image

式はこんな感じ。
=IF(TEXT(B8,"e")="1",TEXT(B8,"ggg元年m月d日"),TEXT(B8,"ggge年m月d日"))

漢数字で表示するには[DBnum1]書式指定文字を使用する。
=IF(TEXT(B7,"e")="1",TEXT(B7,"[DBnum1]ggg元年m月d日"),TEXT(B7,"[DBnum1]ggge年m月d日"))

◆Excelで表移動に対応した行番号を振る

表に項番を振ることはよくある。
image

途中に行挿入されても大丈夫なように「Row() - 4」などという式を入れて後は全ての行にコピーしておけば良い。

と思ったのだ、先頭に行を挿入されたり表自体を移動したりするとずれてしまう。
image

そこでヘッダーの「No」カラムに対して適当な名前をつけて(ここではHeaderNO)、「Row() – Row(HeaderNO)」とかやってあげると良い。
image

2011年4月18日月曜日

◆LightSwitchに注目する

こんな連載が始まった。
@IT情報マネジメント:LightSwitchで情シスを効率アップ 連載インデックス

まさに自分が最近考えていたことと一致する。
ソフトの開発費をハードや運用費で賄う時代はとっくに終わってしまった。
はっきり言って、受託開発などで儲けを出すのは無理な時代だ。
開発にお金を掛けてもペイするのは不特定多数の膨大なユーザーを抱えるシステムだけだろう。
高くて良い物よりも安くてそこそこのものが求められる時代である。
ソフトウエアもそのような分野の比率が高まってくるのではないだろうか。

実はこのところMS関連技術を勉強し始めたのは、このLightSwitchを使ってみたいからである。
LightSwitchのベータ版を触ってみて非常に興味をそそられたのだが、その基盤技術としてEntityFrameworkが使われているらしいと聞き、EntityFrameworkを調べ始めると、そのアクセスにはLINQtoEntityが使われていたり、画面はSilverLightが使われているとか、そうなるとIISに載せてみたいなぁとか、どんどん必要技術が増えていく。

割りきってLightSwitchをブラックボックスとして使ってみようかとも思ったのだが、最終的に細かなカスタマイズをしたくなった時にそのバックグラウンドを知らないのでは若干心もとない。

幸いLightSwitchはまだベータ2の段階で製品版が出るには若干時間があるだろう。ということでLightSwitch本体は軽く情報を抑えつつその間にMS関連技術を俯瞰しておきたい。

何にしても自分のようなお気楽プログラマーには魅力的な存在になりそうだ。

2011年4月17日日曜日

◆グライシンガー今年もか

4月16日広島戦。

グライシンガーがメタメタ。
今年もあまり期待が持てそうにないか・・・。

2番手西村。
見た感じ去年と違いはなさそうにも思うが結構相手打者が打ちづらそうにしていた。
リリーフ向きなのだろうか。
先発だとコースを狙いすぎているような気がする。
今年はある程度期待できるかも。

3番手アルバラデホ。(新外国人)
ん~、微妙。
ヤンキース(3A)では去年抑えで46セーブをあげたそうだが。
球速はそこそこ。
コントロールは比較的良さそう。
変化球もそこそこ。(何が決め球かはよく判らなかった)
フォームがいまいちだな・・・。

4番手金刃。
ストレートが早くなったね。
威力十分。
変化球がキレ、コントロールともにもう一息かな。
変化球次第では大化けしても良いピッチャーだと思うが。

 

試合はラミレスのホームランで2点を先行した後、グラシーンガーがすぐに4点取られて。そのまま終了。
グライシンガー以外はヒットも打たれなかった。

打線は流れが悪く、調子はそこそこだが広島の大したことのないピッチャーを崩せなかった。
小笠原が調子悪そう。(去年ももう一息だったな)
そろそろ衰えてきたか・・・。

やはり打線は阿部が戻ってこないと辛いかも。
亀井もなんとか復調しないのかな。
脇谷が調子良さそうだが、これが本物なら巨人にとっては大きい。

まぁ、今年も決め手に掛ける雰囲気いっぱいだな。

2011年4月16日土曜日

◆LINQでの1対多対多の集計

NorthWindにて以下のような関係になっているテーブル。
image

この3つのテーブルを使って、顧客ごとの注文合計金額を計算してみる。
テーブルの関係は見てわかるとおり、CustomersとOrderが1対多、OrderとOrder_Detailsが1対多。

これをLINQで書く場合、Cutomersから始めるかOrderから始めるか迷うところだが、とりあえずCutomersから始めてみる。

こんな感じだろうか。

            using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orderTotalByCustomer = nwnd.Customers
.Select(c => new
{
c.CustomerID,
c.CompanyName,
total = c.Order.Sum(o => o.Order_Details.Sum(od => od.Quantity * od.UnitPrice))
});

dataGridView1.DataSource = orderTotalByCustomer;

}

Order_Detailsは複数明細もっているのでそれを集計し、それをまた複数Order分集計するといった感じ。


なんとなく良さそうかなと思ったのだが実際に実行してみるとエラーが発生する。


image

普通なら変数の値を追って行きとなるのだが、LINQなんてどうやってデバッグしたものか。
VisualStudioを弄ってあれこれ見てみたがさっぱりチンプンカンプンだ。(^^;


まぁ、こういう時は感で考えるしかない。(本当か?)
とにかく何かがNULLだって言っているんだからそれが何かを考える。しかもDecimalだって言っているのだからDecimalの項目を探してみる。
すると、VisualStudioでマウスカーソルを当てながら見ていくとどうやらOrder_DetailのUnitPriceがDecimalだ。
ん~、VisualStudioって便利になったものだ。(私がちょっと触ってみた2002の頃からはだいぶ進歩している)


あー、そうか、Customerから手繰っているからこいつはLeftOuterJoinイメージになっていて、1件も発注していないCutomerに対するOrder_Detailの金額を計算しようとしてエラーになっているのか。


試しにCustomerを1件だけWhereで抽出して流してみるとうまくいった。
やはりエラーの原因はそれっぽい。


ではどう直すのか。
とりあえず以下のようにしてみた。


using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orderTotalByCustomer = nwnd.Customers.Where(c => c.Order.Count() != 0)
.Select(c => new
{
c.CustomerID,
c.CompanyName,
total = c.Order.Sum(o => o.Order_Details.Sum(od => od.Quantity * od.UnitPrice))
});

dataGridView1.DataSource = orderTotalByCustomer;

}

Orderのカウントを取り0件以外を表示させるって感じ。
するとなんとかうまくいった。


image


めでたし、めでたし。


でも、ちょっとまてよ。
こんなことなら最初からOrderからスタートすれば良かったんだ。
そうすれば0件判定なんかしなくてもOrderのあるデータだけに絞り込まれる。


ん~、でもゼロはゼロで表示させたいことも業務ではしばしばある。
ここはやはり、Customerから手繰ってゼロはゼロで表示させたい。


そうか、GroupJoinの所で使ったDefaultIfEmptyを使ってDefaultのインスタンスを指定すれば良いんだ。


こうかな?


            using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orderTotalByCustomer = nwnd.Customers
.Select(c => new
{
c.CustomerID,
c.CompanyName,
total = c.Order.Sum(o => o.Order_Details
.DefaultIfEmpty(new Order_Details() { Quantity = 0, UnitPrice = 0 })
.Sum(od => od.Quantity * od.UnitPrice))
});

dataGridView1.DataSource = orderTotalByCustomer;

}


うぎゃ。


image


サポートされないオーバーロードって何?
そんなもん、普通コンパイル時に判るもんとちゃうのん?


ZZZ


ん、どちらにしてもOrderがないのにOrder_DetailのDefaultを指定しても仕方がないな。
OrderのDefaultを指定するのか?
でも何を指定すりゃいいの?
こうなりゃ、適当に。


            using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orderTotalByCustomer = nwnd.Customers
.Select(c => new
{
c.CustomerID,
c.CompanyName,
total = c.Order.DefaultIfEmpty(new Order() { OrderID = 1 }).Sum(o => o.Order_Details
.DefaultIfEmpty(new Order_Details() { OrderID = 1, Quantity = 0, UnitPrice = 0 })
.Sum(od => od.Quantity * od.UnitPrice))
});

dataGridView1.DataSource = orderTotalByCustomer;

}

結果は変わらず。(^^;


そうか、OrderとかOrder_Detailとか考えているからダメなんだ。
要は、totalにゼロを入れれば良いのだ。


これでどうだ。


            using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orderTotalByCustomer = nwnd.Customers
.Select(c => new
{
c.CustomerID,
c.CompanyName,
total = c.Order.DefaultIfEmpty().Sum(o => (o == null) ? 0 : o.Order_Details
.Sum(od => od.Quantity * od.UnitPrice))
});

dataGridView1.DataSource = orderTotalByCustomer;

}

おー、出た出た。


image


ちゃんと金額がゼロのやつもある。


でも、合計金額とか本当に正しいのだろうか。
もう疲れたので検証せず(笑)


最後に、これで出力されたSQLはこんな複雑な奴。


image


自分じゃ絶対書けないな・・・。
LINQって凄い?

◆どうしてこうも日本の政治家はダメなのか

こんな記事が載っていた。

ファイル:東日本大震災 国民新・亀井氏が自民へ「復興実施本部」参加呼びかけ

 国民新党の亀井静香代表が13日、自民党の谷垣禎一総裁に電話し、東日本大震災の復興計画を実施する「復興実施本部」への参加を呼びかけていたことが分かった。自民党幹部によると、谷垣氏は「本部がどういう位置付けか分からず、権限も不明確だ」と断ったという。谷垣氏は14日の会見で首相に退陣を求めるなど政権批判を強めている。

なぜ日本の政治家はこの未曽有の有事に当たってこんな政権抗争ばかりをしているのか。
まったく反吐が出そうだ。

政治家にとってはこの災害復興も国民のためではなく自分たちの権力(勢力)強める場でしかないのだろう。

誰も菅総理が素晴らしいリーダーだとは思っていない。
しかし、だからといって今この時に退陣云々などと言って政治を混乱させるのはまったくもって非常識にも程がある。
菅総理がダメならダメでみんなでとにかく協力して盛り上げるのが今あるべき姿では無いのか。

それが、被災県岩手選出の小沢でさえ今まで政治献金問題で小さくなっていたくせにこれ幸いと党首批判を繰り出す始末。

そもそもこれまで長い間政権を牛耳り原発の今の姿をを作ってきたのは自民党ではないのか。
それを、たまたま今の政権が民主党にあるからと言って第3者を装うなんて厚顔無恥も甚だしい。

とにかく今は責任問題などどうでも良いことなのだ。
そんなことは復興が終わって放射能漏れが無くなってからやってくれ。

せっかくみんなで「日本はひとつ」をスローガンに頑張っているのに。

2011年4月15日金曜日

◆澤村デビュー

WEBで結果だけを見た。
まぁそこそこ投げたみたいね。
次はテレビで見たいな。

試合結果は引き分けの様だ。
結果はさておき9回の攻撃が気に入らない。
ノーアウトから1点を取ってなおもランナー2塁に脇谷。
次の小笠原のヒットでホームで憤死となったようだが、いったい誰が3塁コーチをやっているのだろう。
全く持って野球を知らない。ド素人だ。
あと1点取ればだいたい勝負あり、という状況なのだから100%セーフでなければ突っ込んではいけない状況だ。なんたってノーアウトよ。

ノーアウト1,3塁としておけば小笠原に代走鈴木。
3塁走者が脇谷ならそうそう2塁にも投げられない。
かなりの高確率でノーアウト2、3塁だ。
しかも走者が脇谷、鈴木とくればゴロゴーの勝負を2回できる。
9割がたもう1点は確実だったはずだ。

案の定、9回の裏に2アウトから四球を出して1点取られて引き分け。
2点差ならホームランも怖くないので2アウトから四球なんて出さなかったろうに。

こんな大味な野球をやってるから去年みたいに最後に逆転されてしまう。

3塁コーチは猛省すべし。

◆LINQでのコレクション変換処理

LINQの結果として返ってくるのは基本的に、IEnumerable<T>もしくはIQueryable<T>型である。
これを他のコレクションに変換したい場合はLINQに用意されている各種コレクション変換処理を行う。

以下のような感じだ。

            using (Pubs.PubsDataContext pubs = new Pubs.PubsDataContext())
{
Console.WriteLine("■■■配列への変換(ToArray)■■■");
string[] arrFname = pubs.authors.Select(a => a.au_fname).ToArray();
foreach (string name in arrFname) Console.WriteLine(name);

Console.WriteLine("■■■LISTへの変換(ToList)■■■");
var authList = pubs.authors.Select(a => new { a.au_id, a.au_lname, a.au_fname }).ToList();
foreach (var a in authList) Console.WriteLine(" {0} {1} {2} ", a.au_id, a.au_fname, a.au_lname);

Console.WriteLine("■■■辞書への変換(ToDictionary)■■■");
var authDic = pubs.authors.Select(a => new { a.au_id, name = a.au_fname + " " + a.au_lname })
.ToDictionary(an => an.au_id,an => an.name);
foreach (var a in authDic) Console.WriteLine(" {0} {1} ", a.Key,authDic[a.Key]);

Console.WriteLine("■■■マルチ辞書への変換(ToLookUp)■■■");
var auth = pubs.authors.ToLookup(a => a.state, a => a.au_fname);
foreach (var ag in auth)
{
Console.WriteLine(string.Format("◆{0}◆", ag.Key));
foreach (var fname in auth[ag.Key])
{
Console.WriteLine("-- " + fname + " --");
}
}
}

ここで目新しいのがToLookup、マルチ辞書って何だろう。
調べてみるとILookup型を返すようだ。そしてILookupを実装しているのがLookup型。
どうもLookup型とはDictionaryと同様な使い方をするが、Key指定で返ってくるのがコレクションになっているもののようだ。即ちKEYでグルーピングしてくれるわけだ。


グルーピングといえば、以前使ったGroupByがあるが違いはなんだろうと考えてみる。
Linq To SQLで言えば、GroupByはSQLによるグルーピング、Lookupはローカルメモリー内でのグルーピングという事だろう。
これが、Linq to Objectとなるとどちらもローカルメモリー内でのグルーピングとなるのだが、Lookupがダイレクトアクセス用、GrouByがシーケンシャルアクセス用。ダイレクトアクセスが必要なければGroupByのほうが軽い。なんて自分なりには解釈することとした。

◆SQLServerのユーザー認証

SQLServerのユーザー認証方法について覚書として簡単にまとめておく。

認証にはサーバー認証とDB認証の2段階の認証が存在している。

サーバー認証とはインスタンスにログインすること。
この段階ではDB認証を受けていないのでまだDBには個々のアクセス出来ない。
しかし、サーバーの管理作業などはこの状態で可能。
どのような管理作業ができるかはそのユーザーが所属するサーバーロールによる。(サーバーロールとはWindowsドメインで言うところのドメインユーザーグループみたいな物)
sysadminが最上位特権のサーバーロールで、ここに所属しているとすべての作業が可能だ。インストールしたときのユーザーがここに所属しているのが普通かな。
image

DBにアクセスするためにはサーバー認証を通った上でDBにログインする必要がある。
上記画面でサーバーログインユーザーを作り、固定サーバーロールを割り当てた後、ユーザーマッピング欄からDBログインにも追加できるようになっているのでアクセスしたいDBを指定し、適切なデータベースロールを指定すれば良い。image

また、サーバーロールの最上位特権であるsysadminロールは若干特別なロールで、ここに所属するユーザーは全てのDBのdboユーザーにマッピングされる。(dboユーザーは全てのDBに自動的に存在する)
dboユーザーはdb_ownerデータベースロール(データベースロールの最上位特権)に含まれているので結果的にsysadminサーバーロールにユーザーを追加すると、サーバーに対しても全DBに対しても最上位の特権を持つことになる。

そこで、DBを作るときはsysadminサーバーロールに所属するユーザーで作っておけばdboがオーナー(スキーマ?)になるので、テーブル名の指定だけでアクセスできて便利である。

逆にスキーマ分けしたいのであれば、それぞれのDBユーザーに所属するサーバーログインでログインしてからDBを作成すれば良いことになる。

管理者以外の一般ユーザーにはdb_datareaderとdb_datawriterロールを割り当てておけば良いだろう。

◆Excelで日付に対応する曜日を表示する

きっとFAQであろう。

簡単なのは日付が入されえているセルを選んで書式設定変更する。

image

“aaa”で日、月、火表示。
”aaaa” で日曜日、月曜日、火曜日を表示される。

WEEKDay関数とCHOOSE関数組み合わせる定番方法もある。
=CHOOSE(WEEKDAY(E4),"日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日")

また、数値形式になっている場合の値を日付型にするはDATE関数とMID関数を組み合わせる。
=DATE(MID(E11,1,4),MID(E11,5,2),MID(E11,7,2))

ちなみに、Excelで関数入力するときはShft + F3で関数入力ダイアログを表示させると便利だ。 

=DATE(MID(E11,1,4),MID(E11,5,2),MID(E11,7,2))

◆天気を表示するガジェット

天気を表示する標準のガジェットって当日の天気を表示するだけじゃ使えな~い。
と思ってずっと使っていなかったのだが、大きいサイズで表示させると向う3日の予報を表示してくれるのに今更気がついた。
image

ちょっとだけ嬉しいかも。

2011年4月14日木曜日

◆プロ野球開幕

震災の影響で遅れていたプロ野球がやっと開幕した。

こちらはもろに被災県なので、プロ野球どころではないという空気でいっぱいなのだが、40年来の巨人ファンとしてはやはり気になり録画してざっとは見てみた。

開幕連勝でスタートとなったが、今年の戦力はどうなのだろう。
オープン戦とかは全く見ていないのでよく判らない。

新外国人が打線に一人入ったようだがどうせ活躍はしないだろう。

坂本、脇谷、長野あたりは悪くなさそうだ。
しかし、クリーンアップが小笠原、ラミレス、高橋といったベテランに頼らざるを得ないのはちょっと心配なところ。
そろそろ新しい人に出てきてほしいものだ。
最短距離にいると思われた亀井が去年の体たらくから今年もスタメンを外れているようでは若干さびしい。
松本も出ていなかったがどうしているのやら。

ヤクルトの打線がちょっと寂しい感じだったのと、流れが巨人に向いていたので勝つには勝ったが強いという感じではなかった。

東野は去年と同じくらいの感じ。
悪くは無いのだが、絶対的エースになるにはやはり決め球が欲しい。
一つくらい新しい球種を覚えるくらいの努力が欲しいな。
内海は去年よりキレがあるように感じたが、所詮超一流までは行けないピッチャーだろう。

次はだれが来るの?
外国人とかどうなっているのだろう。

やはり今年の目玉は澤村だけか?
早く見てみたい。

抑えは山口がやっていたがクルーンの代わりは取らなかったのかなぁ。
山口でも良いとは思うが、そうするとセットアッパーが弱そう。
久保以外に誰かいるのか?(本当は久保は先発向きだと思うのだが)

そういえば澤村に背番号を取られてしまった木村はどうしてる。
けがは治ったのかな・・・。

◆Windowsフォームアプリからコンソールへ出力する

LINQのサンプルを試していると階層構造になったオブジェクトの中身をリストしたい場合が出てくる。
標準では階層構造を表示してくれるWindowsコントロールは見当たらないため2重ループとかやって自分でコンソール出力なんてことになる。

Windowsフォームアプリからのコンソール出力はVisualStudioだと出力ウインドウに表示されるのだが、出力ウインドウはそれ以外の情報も色々出力されるので結果が見づらい。

いつもならメッセージボックスに表示させるのだが、出力量が多いとこれもまた今ひとつ。

コンソールアプリのように、普通にコンソール出力できないものかと今更ながら調べてみる。

きっと、そう簡単ではないだろうと(コンソールアプリで作ってそこから自分でフォームを表示させてなんてのは勘弁ねと)思ったのだが実に簡単にできることが判った。

プロジェクトのプロパティでコンソールアプリに変更するだけで良いんだと。

へーーーーーーーーーって感じ。

2011年4月12日火曜日

◆ASP.NET(IIS)からのリソース(DB)アクセス

実運用システムではサーバー屋さんがやってくれるので知らなくても良いが、基礎知識としてメモしておく。(自分用なのでとりあえず動けばOKの簡易的な設定)

昔はMS製品というとインストールさえすれば動いたので、素人な私には非常に楽ちんだった。
なのに、世の中に悪い人たちが沢山いるせいでデフォルトでは何も動かない初期設定が基本になってきた。(TrustWorthy)

なので、開発の前にまずは動かす環境を作る必要がある。(VisualStudioなどはローカルで全部できるようになっているので作るだけは作れるが他人に提供するとなるといきなり困ってしまう)

ASP.NETのアプリケーションはIISのワーカープロセス(アプリケーションプール)でホストされて動く。
なので、ASP.NETからDBなどにアクセスしようとすると、ユーザーID的にはワーカープロセスサービス(w3wp.exe)を実行しているサービスアカウントでアクセスしに行くと言う事になる。(通常は)
IIS6やIIS7ではnetwork serviceアカウントがデフォルトで使われているので、そのままでは(外部の)DBにアクセス出来ない。

そこで以下の手順でサービスの実行アカウントを変更する。(ドメイン環境前提)

  1. ドメインに専用のユーザーを作成。(Domain Users権限)
  2. SQLServerにそのユーザーを追加し適切な権限を付与する。
  3. IISをホストするサーバーのローカルセキュリティポリシーでローカルログオンを拒否。
    image
  4. 作成したユーザーをIISをホストしているサーバーのIIS_WPGローカルグループに追加する。
    って、IIS7環境にはそんなローカルグループは無いじゃん。
    どうやら、IIS_IUSRSに変更になったらしい。
    (アプリケーションプールの実行アカウントに設定すると勝手に追加されるっぽい情報もあり。)
    image
  5. IISマネージャでアプリケーションプールの実行アカウントに指定する。
    image

ちなみにドメイン環境がない場合はIISとDBを同じサーバーで動かすか、ミラーアカウント(だっけ?)、即ちIISのサーバーとDBのサーバーに同じID/PWDのユーザーを作って運用すればとりあえず可能ではないだろうか。

また、DBでの認証にSQLServer認証を使う場合にはASP.NETアプリケーションにて接続文字列にUID/PWDを指定してアクセスすることになるのかな。

最後に、ワーカープロセスからWindows統合認証でSQLServerにアクセスする際にアプリケーションプールの実行ユーザーではなくクライアントユーザーを使ってアクセスする方法がある。(あまり使われないようだが)
これを使用するとデータベースのアクセスログに実際のクライアントユーザーが記録されるというメリットがある。
これは「偽装」と呼ばれる方法でweb.configのsystem.web要素に以下のように指定する。

<identity impersonate="true" 
userName="domain\username"
password="password"/>


userNameとpasswordをこの様に固定することもできるし、これを省略するとIISで認証をうけたWindowsユーザーが使われる。
ただし、匿名認証が使われている場合はIIS側で匿名に割り当てるユーザーを指定できる。
image


ん~、この画面を見るとIISからも偽装を設定できるようになったっぽい。
image


 


Windows Server2008R2およびWindows7から(IIS7.5?)はアプリケーションプールの実行アカウントのデフォルトが「Network Service」から「アプリケーションプール名(ID)」に変更になったようだ。
このIDにファイルのアクセス権を与えるには、ACL設定画面で「IIS AppPool\DefaultAppPool」などと指定すれば良い。

◆Excelの条件付き書式を使ってみる

まずはよくあるサンプルでスケジュール表の当日行の色を変えてみる。

  1. 範囲を選択(タイトル部分は選択しない)
    image
  2. 「ホーム」「条件付き書式」「新しいルール」を選択。
  3. 新しい書式ルールで「数式を使用して、書式設定するセルを決定」を選択し、式に「=$A3=ToDay()」を指定する。
    image
  4. 書式ボタンをクリックして条件を満たしたときに設定したい書式を指定する。
  5. 後はOKで閉じていくと、当日の行に指定した書式が設定される。
    これで、Bookを開くたびに当日行が強調表示されることになる。
    image

例をもう一つ。
以下のような表示で設計と製造の進捗を管理している。
どちらかが「中止」になったときは両方共「中止」するルールがあったときに、不一致になっている部分が判るよう条件書式を設定する。
image

今度は、行が追加されても良いように設計列と製造列全体を選択して書式設定する。
image

あとは先ほどと同じ手順でルールを設定する。
image

結果はいかの通り。
image

式はこっちのほうが良いのかな。(not equalってこうだっけ?)
=($N1="中止")<>($P1="中止")

2011年4月11日月曜日

◆LINQでの外部結合処理

通常Joinには内部結合と外部結合がある。
内部結合は既にJoinの所でやったので今度は外部結合である。

リレーションが張ってあるテーブルではちょっとやりずらいのでいつもお世話になっている@ITのサイトの例を参考にさせてもらった。
第7回 LINQ応用編 - @IT

Joinの所でも思ったのだが、どうもこの自分でテーブルを結合させるパターンの拡張メソッド方式は判りづらい。というか文法的に相性が悪いように思う。
はっきり言って素のSQLよりはるかに複雑になってしまう。
これでは本末転倒というものだ。

そこで今回は先に埋め込みクエリー方式でやってみた。(というか上記サンプルが埋め込みクエリー方式で書いてあるので)

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

namespace ConsoleApplication3
{
class Program
{
class 商品情報
{
public int Id;
public string 名前;
}

class 商品販売価格
{
public int Id;
public string 店名;
}

static void Main(string[] args)
{
商品情報[] 商品情報データ =
{
new 商品情報() { Id=1, 名前="PC-8001" },
new 商品情報() { Id=2, 名前="MZ-80K" },
new 商品情報() { Id=3, 名前="Basic Master Level-3" },
new 商品情報() { Id=4, 名前="COMKIT 8060" },
};

商品販売価格[] 商品販売価格データ =
{
new 商品販売価格() { Id=1, 店名="BitOut" },
new 商品販売価格() { Id=1, 店名="富士山音響" },
new 商品販売価格() { Id=2, 店名="富士山音響" },
new 商品販売価格() { Id=3, 店名="マイコンセンターROM" },
new 商品販売価格() { Id=3, 店名="富士山音響" },
};

//埋め込みクエリー方式での外部結合
var query = from x in 商品情報データ
join y in 商品販売価格データ on x.Id equals y.Id into z
from a in z.DefaultIfEmpty(
new 商品販売価格() { 店名 = "取り扱い店なし" })
select new { Name = x.名前, 店名 = a.店名 };

foreach (var 商品 in query)
{
Console.WriteLine("{0}", 商品.Name);
Console.WriteLine("\t{0}", 商品.店名);
}

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

//埋め込みクエリー方式での外部結合2
var query2 = from x in 商品情報データ
join y in 商品販売価格データ on x.Id equals y.Id into z
from a in z.DefaultIfEmpty()
select new { Name = x.名前, 店名 = a == null ? "取り扱い店なし" : a.店名 };

foreach (var 商品 in query2)
{
Console.WriteLine("{0}", 商品.Name);
Console.WriteLine("\t{0}", 商品.店名);
}

Console.WriteLine("----------------------------------------");
//拡張メソッド方式での外部結合
var query3 = 商品情報データ.GroupJoin(商品販売価格データ,
x => x.Id,
y => y.Id,
(x, g) => new
{
t1 = x,
t2 = g
})
.SelectMany(t => t.t2.DefaultIfEmpty(
new 商品販売価格() { 店名 = "取り扱い店なし" }),
(t, a) => new
{
Name = t.t1.名前,
店名 = a.店名
});
foreach (var 商品 in query3)
{
Console.WriteLine("{0}", 商品.Name);
Console.WriteLine("\t{0}", 商品.店名);
}
}
}
}
商品情報クラスと商品販売価格クラスを1対多の関係で使っている。

埋め込みクエリー方式では商品情報と商品販売価格をJoinしてIDが一致した商品販売価格のコレクションをzに入れている。
その後、商品販売コレクションのzを再度Fromに指定して商品情報と商品販売価格の内容をSelectしている。(このタイミングというかコンテキストで商品情報のxを参照できるのが、拡張メソッド方式に比べて埋め込みクエリー方式を簡単にしている理由ではなかろうか)


ここでこれまで出てきていないDefaultIfEmpty拡張メソッドなるものが登場している。
これは、外部結合(ここでは左外部結合)では、一致する右側のデータが存在しない場合がある。
その時に表示させるDefault値を指定するのがDefaultIfEmptyだ。
この例ではzが商品販売価格のコレクションなので、商品販売価格のオブジェクトをnewで作って指定している。


2つ目の埋め込みクエリー方式の例ではこの部分でデフォルトオブジェクトを指定せずにSelectの段階で商品販売価格がnullかどうかを判定し、デフォルト値を指定している。
それならば、DefaultInEmptyは必要ないのでは?という感じを抱くかもしれないが、これがないと外部結合にならない。(Id=4, 名前="COMKIT 8060"のデータが出力されない)
DefaultIfEmptyがない場合はnullではなく中身が空のオブジェクトになるようだ。


3つめが拡張メソッド方式。
73行目~77行目で商品情報クラス(x)に商品販売クラスのコレクション(g)を結合させたオブジェクトを作っている。
SelectManyでxとyの全パターンの組み合わせを作り出した上で新たな匿名クラスを作り出している。
判りづらいので図を使って説明すると。(図示し辛いので改行位置を変えた)
image


SelectManyの役割はパイプライン入力である赤で囲った匿名クラスのコレクションと、青で囲った部分のコレクションの組み合わせを作ることである。
赤がLEFT OUTER JOINの左側、青が右側。
そしてそれぞれの子要素を(t,a)として参照している。(tが赤の子要素,aが青の子要素)
青で囲んだ部分は極端な話、何も処理せずにtとは無関係なコレクションを指定してもよいのだが、ここでは入力パイプラインの子要素に対する前処理が可能になっているのでDefaultIfEmptyを使ってデフォルト値を補っている。


参照の変遷を書くとこんな感じだろうか。
image


何度も変数名が変わり非常に煩雑なのが判る。
素人考えだがLeftOuterJoinメソッドを作ってこんな感じにはならないものだろうか。


var query = 商品情報データ.LeftOuterJoin(商品販売価格データ,
    x => x.Id,
    y => y.Id,
    g => g.DefaultIfEmpty(new 商品販売価格(){店名 = “無し”}),
    a = > new{Name = x.名前,店名 = a.店名);

◆Windows PE 徹底活用バイブル ― PE 3.1完全対応版(メモ)

 

【解説】[新]Windows PE 徹底活用バイブル~基礎編― 最新のWindows PE 3.1に完全対応! : Windows Server - Computerworld.jp

【解説】[新]Windows PE 徹底活用バイブル~ブータブルメディア作成編 ― 最新のWindows PE 3.1に完全対応! : Windows Server - Computerworld.jp

◆SetPointのバグに悩む

SetPointとはLogicoolマウスのマウスウェア。
昔からその品質の悪さには定評がある。(MSがもう少し多機能マウス路線になってくれればなぁ)

最近になってだいぶ落ち着いてあまり不満もなくなっていたのだが、今回スリープ復帰した後にキーアサインが効かなくなる不具合が発覚したため最新バージョンにUPした。

するとスリープ復帰の不具合はめでたく解消したのだが、アプリケーション毎のキーアサインが再起動すると元に戻るようになってしまった。(昔もこの手の不具合があったなぁ)


ちなみにバージョンは、6.2かな。
image

以前はアプリケーション毎設定をするには、そのアプリケーションに対してすべてのボタンに機能を設定する必要があったが、今回のバージョンではカスタマイズしたいボタンにだけキー設定をすれば良いように変わっている。(これば便利)
共通設定通りで良いボタンについては「アプリケーション固有のタスクはありません」にしておけばよい。
image

おそらく、この新機能の作り込みでバグが混入したのではなかろうか。

今までのキーアサインに慣れているのでこのままではストレスが溜まりまくり。
なんとか回避策を探る。(以前のバージョンに戻すことも考えたが、それではスリープ復帰の不具合が解消されないので)

最初は書き込み権限の問題等で設定を保存で規定なのかとも思ったが、共通設定は保存できているのでそれな無さそうだ。
デフォルト設定で上書きされて戻されているのかとも思い、
"C:\Program Files\Logicool\SetPointP\default.xml"を弄ってみたりもしたのだがどうも違うっぽい。

ちなみにユーザー設定は以下のファイルに保存されているっぽい。
"C:\Users\minminnana\AppData\Roaming\Logitech\SetPoint\user.xml"

アプリケーショ固有の設定をして保存すると、このファイルのタイムスタンプが更新されている。
実はこれにちょっと騙されていた。共通設定を変更したときは確かにこの設定ファイルが更新されているのだが、アプリケーション毎の設定をした後、中身をコンペアしたらなんと何も変わっていないではないか。

中身を解析してみるとアプリケーション毎の設定もここに保持する仕様のように見える。
アプリケーション毎設定がされた時に内容を保存する処理が漏れているなんて単純な話?(なんかバグとも呼べないレベル)

そこで、仕方が無いのでこの設定ファイルを直接編集することとした。
中程のDevice要素で機能設定しているのが見て取れる。
image

すこし下がっていくとアプリケーション毎設定の場合はこんな感じでオーバーライドしている。

            <Button Number="5" Name="5">
<Param IconLoc="" Type=""/>
<Trigger Class="ButtonPress">
<Param Button="5" EventType="100663297" FirstRepeatDelay="0" RepeatDelay="0" Silent="0" Type="0"/>
<TriggerState Name="ButtonDownUp" HandlerSet="KeystrokeAssignment">
<Handler Class="KeystrokeAssignment">
<Param DisplayName="Alt+N" LParam="540082177" Modifier="0" VirtualKey="78"/>
</Handler>
<AppOverride App="mm.exe" HandlerSet="AppOverride_MindMapperBack">
<Handler Class="AppOverrides">
<Param ActionName="BACK" ExeName="MM.exe"/>
</Handler>
</AppOverride>
<AppOverride App="winword.exe" HandlerSet="KeystrokeAssignment">
<Handler Class="KeystrokeAssignment">
<Param DisplayName="Alt+N" LParam="540082177" Modifier="0" VirtualKey="78"/>
</Handler>
</AppOverride>
<AppOverride App="msimn.exe" HandlerSet="AppOverride_OutlookExpressBack">
<Handler Class="AppOverrides">
<Param ActionName="BACK" ExeName="MSIMN.exe"/>
</Handler>
</AppOverride>
<AppOverride App="outlook.exe" HandlerSet="KeystrokeAssignment">
<Handler Class="KeystrokeAssignment">
<Param DisplayName="Alt+N" LParam="540082177" Modifier="0" VirtualKey="78"/>
</Handler>



キーコードは基本的にアスキーコードだが特殊キーはちょっと違っているようだ。LParamというのはなんの意味があるのか判らないが、とりあえず気にしなくても支障はなさそう。
設定の仕方が解らないときは一旦共通設定に設定してみてその内容を移植すると良い。



ひとつ解らなかったのが、Office系のソフトは予めいくつかのアプリケーション設定がデフォルト設定されているのだが、単純にそのAppOverrideを削除しても「アプリケーション固有のタスクにありません」には戻ってくれない。
だいぶ面倒くさくなってきたので、共通設定と同じ設定内容でオーバーライドする事でお茶を濁した。


-- 以下追記 2014/02/13


MTG Blog: ◆SetPointでまた悩む(解決) \(^o^)/

2011年4月10日日曜日

◆LINQでのグループ化結合処理(GroupJoin)

NorthwindのOrderとOrder_Detailのように1対多で多を集計していく処理。
MTG Blog: ◆LINQで明細集計処理で行ったのと同じ処理をリレーションが張られていないものとしてGroupJoinを使って集計してみる。

自分でJoinしなければいけない分やはり若干面倒な処理となる。

        //グループ化結合処理(GroupJoin)
public void linqGroupJoin(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
var sumPrice = nwnd.Order.GroupJoin(nwnd.Order_Details,
o => o.OrderID, od => od.OrderID,
(o, g) => new
{
o.OrderID,
o.CustomerID,
price = g.Sum(od => od.Quantity * od.UnitPrice)
});
form.dataGridView1.DataSource = sumPrice;
}
}

8行目でOrder_Detailをグループ化したコレクションをgで参照するよう指定している。
あとは、リレーションが張られているときと同様にSum拡張メソッドで集計してあげればOKだ。


埋め込みクエリー方式はこちら。


        //グループ化結合処理(GroupJoin)
public void linqGroupJoin(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
var sumPrice = from o in nwnd.Order
join od in nwnd.Order_Details on o.OrderID equals od.OrderID into g
select new
{
o.OrderID,
o.CustomerID,
price = g.Sum(od => od.Quantity * od.UnitPrice)
};
form.dataGridView1.DataSource = sumPrice;
Console.Beep();
}
}


グループ化後のOrder_Detailをintoでgに入れている。

◆LINQでの内部結合(Join)処理

基本的にリレーションが張られたテーブル間でJoin処理は不要なのだが、中にはリレーションが張られていない場合もある。(ちなみに私の周りではみんな張っていない)
そのような場合は自分でJoinでくっつけて参照する必要がある。
まぁ、通常SQLではそうやっているので違和感は無い。
また、Linq to DataSetなどの場合も自分でJoinする必要がありそうだ。

以下の例ではNorthWindのOrderとEmployeeをJoinしている。(本来はリレーションが張られているので必要ない)

        //内部結合(Join)処理
public void linqJoin(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orderEmp = nwnd.Order.Join(nwnd.Employees, o => o.EmployeeID, e => e.EmployeeID,
(o, e) => new
{
o.OrderID,
o.OrderDate,
orderemployee = e.LastName + " " + e.FirstName
});
form.dataGridView1.DataSource = orderEmp;
}
}

ん~、いまひとつピンとこない文法だ。


埋め込みクエリー方式で書くと以下の通り。



        //内部結合(Join)処理
public void linqJoin(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
var orderEmp = from o in nwnd.Order
join e in nwnd.Employees
on o.EmployeeID equals e.EmployeeID
select new
{
o.OrderID,
o.OrderDate,
orderemployee = e.LastName + " " + e.FirstName
};
form.dataGridView1.DataSource = orderEmp;
Console.Beep();
//Joinを使わない書き方
orderEmp = from o in nwnd.Order
from e in nwnd.Employees
where o.EmployeeID == e.EmployeeID
select new
{
o.OrderID,
o.OrderDate,
orderemployee = e.LastName + " " + e.FirstName
};

}


珍しくこちらのほうが判り易い。
Joinを使わない書き方が一番すっきりとして判り易い気がする。

◆LINQで1対多多1のグループ集計処理

以下のstores,sales,titlesテーブル間で、店ごとの売上グループ集計処理を考えてみる。
20110410102751 

テーブルの関係はstoresとsalesが1対多。salesとtitlesが多対1である。
salesテーブルに売上を持っていれば、これまでやったようにstoresテーブルから手繰ってSUM拡張メソッドでsalesの売上を合計するだけだ。
しかし、salesには数量とtitle_idだけがあり、titleの単価はtitlesテーブルに持っているので、salesからまたその先のtitlesまで手繰らないといけない。

以下のような感じになる。

        //1対多対1のグループ集計処理
public void linqGroup1Many1(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var salesByStore = pubs.stores.Select(st => new
{
st.stor_id,
st.stor_name,
salesSum = st.sales.Sum(sls => sls.qty * sls.titles.price)
});
form.dataGridView1.DataSource = salesByStore;
}
}

salesオブジェクトのtitlesプロパティからpriceを手繰って持ってこれるのでそれ程難しいことはなさそうだ。(以前のグループ集計処理とほとんど同じ)


埋め込みクエリー方式では以下のとおり。


        //1対多対1のグループ集計処理
public void linqGroup1Many1(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var salesByStore = from st in pubs.stores
select new
{
st.stor_id,
st.stor_name,
salesSum = st.sales.Sum(sls => sls.qty * sls.titles.price)
};
form.dataGridView1.DataSource = salesByStore;
Console.Beep();
}
}

2011年4月9日土曜日

◆LINQで複合キーを使った集計処理

複合キーを匿名データ型で指定する以外は通常のグループ集計処理と同じ。
この様な処理ではあまりLINQのメリットは感じられない。(SQLで書いたほうが分かりやすく簡単かも)

        //複合キーでのグループ化処理
public void linqGroupComplex(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var customers = nwnd.Customers.GroupBy(c => new {c.Country,c.City})
.OrderBy(g => g.Key.Country)
.ThenBy(g => g.Key.City)
.Select(g => new
{
g.Key.Country,
g.Key.City,
count = g.Count()
});
form.dataGridView1.DataSource = customers;
}
}

埋め込みクエリー方式では以下のとおり。


        //複合キーでのグループ化処理
public void linqGroupComplex(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
var customers = from c in nwnd.Customers
group c by new{c.Country,c.City} into g
orderby g.Key.Country,g.Key.City
select new
{
g.Key.Country,
g.Key.City,
count = g.Count()
};
form.dataGridView1.DataSource = customers;
Console.Beep();
}
}

◆LINQで見出し、明細、合計を出力するグループ処理

明細データを出力しながら、グループタイトルとグループ合計を出力するような処理は帳票でよく見られる。
そのようなパターンをLINQでやってみたいと思う。

        //見出し、明細、合計を出力するグループ処理
public void linqGroupList(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var titleGroup = pubs.titles.GroupBy(t => t.pub_id)
.Select(g => new
{
pub_id = g.Key,
PriceSum = g.Sum(t => t.price),
titlesData = g
});
foreach (var tg in titleGroup)
{
Console.WriteLine("◆" + tg.pub_id + "◆");
foreach (var t in tg.titlesData)
{
Console.WriteLine(string.Format("- {0} \t {1} \t {2}", t.title_id, t.title, t.price));
}
Console.WriteLine("-- 合計金額 --(" + tg.PriceSum + ")");
}
}
}

基本的にはMTG Blog: ◆LINQでマスターを使わないグループ集計でやったLINQと同じパターンで集計している。
明細出力用に、titlesDataとしてtitleクラスのコレクションをグループ毎に保持している。
あとは自前でループを回しながらグループタイトルとそのなかの明細を出力している。


結果は以下のとおり。


SELECT [t0].[title_id], [t0].[title], [t0].[type], [t0].[pub_id], [t0].[price], [t0].[advance], [t0].[royalty], [t0].[ytd_sales], [t0].[notes], [t0].[pubdate]
FROM [dbo].[titles] AS [t0]
WHERE ((@x1 IS NULL) AND ([t0].[pub_id] IS NULL)) OR ((@x1 IS NOT NULL) AND ([t0].[pub_id] IS NOT NULL) AND (@x1 = [t0].[pub_id]))
-- @x1: Input Char (Size = 4; Prec = 0; Scale = 0) [0736]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

◆0736◆
- BU2075 You Can Combat Computer Stress! 2.9900
- PS2091 Is Anger the Enemy? 10.9500
- PS2106 Life Without Fear 7.0000
- PS3333 Prolonged Data Deprivation: Four Case Studies 19.9900
- PS7777 Emotional Security: A New Algorithm 7.9900
-- 合計金額 --(48.9200)
SELECT [t0].[title_id], [t0].[title], [t0].[type], [t0].[pub_id], [t0].[price], [t0].[advance], [t0].[royalty], [t0].[ytd_sales], [t0].[notes], [t0].[pubdate]
FROM [dbo].[titles] AS [t0]
WHERE ((@x1 IS NULL) AND ([t0].[pub_id] IS NULL)) OR ((@x1 IS NOT NULL) AND ([t0].[pub_id] IS NOT NULL) AND (@x1 = [t0].[pub_id]))
-- @x1: Input Char (Size = 4; Prec = 0; Scale = 0) [0877]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

◆0877◆
- MC2222 Silicon Valley Gastronomic Treats 19.9900
- MC3021 The Gourmet Microwave 2.9900
- MC3026 The Psychology of Computer Cooking
- PS1372 Computer Phobic AND Non-Phobic Individuals: Behavior Variations 21.5900
- TC3218 Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean 20.9500
- TC4203 Fifty Years in Buckingham Palace Kitchens 11.9500
- TC7777 Sushi, Anyone? 14.9900
-- 合計金額 --(92.4600)
SELECT [t0].[title_id], [t0].[title], [t0].[type], [t0].[pub_id], [t0].[price], [t0].[advance], [t0].[royalty], [t0].[ytd_sales], [t0].[notes], [t0].[pubdate]
FROM [dbo].[titles] AS [t0]
WHERE ((@x1 IS NULL) AND ([t0].[pub_id] IS NULL)) OR ((@x1 IS NOT NULL) AND ([t0].[pub_id] IS NOT NULL) AND (@x1 = [t0].[pub_id]))
-- @x1: Input Char (Size = 4; Prec = 0; Scale = 0) [1389]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

◆1389◆
- BU1032 The Busy Executive's Database Guide 19.9900
- BU1111 Cooking with Computers: Surreptitious Balance Sheets 11.9500
- BU7832 Straight Talk About Computers 19.9900
- PC1035 But Is It User Friendly? 22.9500
- PC8888 Secrets of Silicon Valley 20.0000
- PC9999 Net Etiquette
-- 合計金額 --(94.8800)

結果は良さそうだが、発行されているSQLを見ると先頭及びグループ出力ごとに発行されているのが判る。
LINQは、それ自身を定義したタイミングでSQLを発行するのではなく実際にデータ必要になったときに発行される仕組みになっているらしい。(遅延ロード機能)
そこら辺を最適化してあげる必要がありそうだが、それはまたあとで検討(勉強)する。

2011年4月8日金曜日

◆家電も再起動が大事?

東北大震災で停電して以来、冷蔵庫の自動製氷が動作しなくなった。
あちこち弄ってみたり、掃除してみたりしたが変わらず。
修理を呼べるような状況でも無かったので毎日手作業で氷を作っていた。

そこへ、夕べの余震でまた停電。
今日、復電したらなぜか製氷機が動き始めた。

やはり冷蔵庫も稼働中の電源断には弱く、おかしくなったらまずは再起動という事なのだろう。
冷蔵庫は直ったのだが、こんどは風呂の自動機能が怪しい。
そういえば、ちょうど夕べ稼働中に停電になった。

やはり再起動だろうか・・・。

◆LINQでマスターを使わないグループ集計

MTG Blog: ◆LINQでマスターを使ったグループ集計処理と同じものをマスターを使わずにGroupBy拡張メソッドを使ってやってみたのだが、そもそも最初に書いたサンプルがまずくてマスター項目を使用して金額を合計しているので結果的にはマスターも参照する必要が出てしまった。

とりあえず同じ結果がでるか試したかったので作ってみた。
要は前回マスター側を主として合計処理を行ったのに対して、今度はトランザクション側を主と見てグループ集計する物である。本来は単一テーブルに対して使うのがメインなのかもしれない。

            using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var titleSales = pubs.sales.GroupBy(s => s.title_id)
.Select(g => new
{
title = g.First().titles.title,
sumPrice = g.Sum(s => s.qty * s.titles.price)
});
form.dataGridView1.DataSource = titleSales;
}
MessageBox.Show("sales合計を表示しました。次にorder合計を表示します。");
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
var orderSum = nwnd.Order_Details.GroupBy(od => od.OrderID)
.Select(g => new
{
OrderID = g.Key,
SumPrice = g.Sum(od => od.UnitPrice * od.Quantity)
});
form.dataGridView1.DataSource = orderSum;
}

後半部分は参考までに単一テーブル(Order_Details)でのグループ集計を付け加えた。


マスターからみて集計した場合とトランザクションから見て集計した場合で基本的には同じ結果が得られるのだが、ちょっとだけ違うところがある。
当然のことながらトランザクション側から見た場合は一つも売れていないtitleは表示されないことになる。


また、7行目と8行目で2度titleテーブルを参照しているためにそれぞれがサブクエリーとなってしまってSQL的には(素人の私がみても)よろしくないことが判る。
もう少しベターな書き方がありそうだが、ここではあくまでもGroupByの使い方がメインなので良しとする。


このように簡単なコーディングからすごく複雑なSQLが発行されてしまうことには閉口する部分もあり、感嘆する部分もありといった感じ。


業務プログラムなどでは、いちいち発行されるSQLを確認しながらじゃなと使えないとなれば、最初から自分でSQLを組んだほうが良いという人も居るだろう。
完全にブラックボックス化されていない技術は確かに使いづらい。
ASP.NETを使ったときにもそのような感じがした。


ただし、完璧なものじゃないと使わない。
原始的に全部自分でやるのがベスト。
とも思わない。


ケースバイケースで適した技術を選択していくのが正しい姿ではなかろうか。


私自身はマイツール的なものやせいぜい身内で使うちょっとしたシステムくらいしか作らないお気楽プログラマーなので、今回のサンプル程度の労力で正しい結果が得られるのであれば十分使える局面はあると思っている。

◆LINQでマスターを使ったグループ集計処理

私が会社に入った頃、プログラミングといえばバッチ処理が殆どで、「ソート」「マッチング」「ブレーク集計」が3種の神器と呼ばれていた(かどうかは知らない)が、だいたいこれさえ知って入れば大抵のプログラミングは出来た。(良い時代だったなぁ)

以下のサンプルで記述している処理などはまさにソートしてマッチングしてブレーク集計してと、3本のプログラムを繋ぎあわせて行っていたような処理である。

それが今ではこんなに簡単に掛けてしまう。
時代の進化ですかね。(そもそもRDBがなかったからSQLさえ無かった)

        //マスターを使ったグループ集計
public void linqMasterGroupSum(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var titleSales = pubs.titles.Select(t => new
{
t.title,
sumPrice = t.price * t.sales.Sum(s => s.qty)
});
form.dataGridView1.DataSource = titleSales;
}
}

titlesテーブルを順に読んでそれにぶら下がっているsalesの数量を合計し金額と掛けてそれぞれの売上を求めている。


埋め込みクエリー方式では以下のようになるが、合計する部分は拡張メソッドとの混在になるようだ。


        //マスターを使ったグループ集計
public void linqMasterGroupSum(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var titleSales = from t in pubs.titles
select new
{
t.title,
sumPrice = t.price * t.sales.Sum(s => s.qty)
};
form.dataGridView1.DataSource = titleSales;
Console.Beep();
}

あれ、テーブル関係の意味合い的にはちょっと違うがMTG Blog: ◆LINQで明細集計処理と同じ処理だった。(^^;


こちらは親子関係じゃ無いだけで所詮1対多だか当たり前か・・・。


2011年4月6日水曜日

◆LINQで読み飛ばし処理

テーブルをキー順で並べておいて何件目から何件という風にデータを取ってくる処理。
WEBページをスクロールするときに1ページ分のデータを取得するときのようなイメージだ。

OrderBy拡張メソッドでソートしておいてSkipで読み飛ばしTakeで指定した件数だけ持ってくるといった処理になる。

        //読み飛ばし処理
public void linqSkipTake(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
nwnd.Log = Console.Out;
var orders = nwnd.Order.OrderBy(o => o.OrderID).Skip(3).Take(5);
form.dataGridView1.DataSource = orders;
}
}

ちなみに発行されているSQLはこんな感じになっている。


SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[RequiredDate], [t1].[ShippedDate], [t1].[ShipVia], [t1].[Freight], [t1].[ShipName], [t1].[ShipAddress], [t1].[ShipCity], [t1].[ShipRegion], [t1].[ShipPostalCode], [t1].[ShipCountry]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[OrderID]) AS [ROW_NUMBER], [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [dbo].[Orders] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p0 + @p1
ORDER BY [t1].[ROW_NUMBER]
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [3]
-- @p1: Input Int (Size = -1; Prec = 0; Scale = 0) [5]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

はっきり言って私にゃチンプンカンプン。
処理速度的にこれが最適か?って話は別として、お気楽プログラマーにとってLINQって素晴らしい。(^^;

◆LINQで明細集計処理

注文と注文明細の様に親子関係のテーブルはよくある。
なので、注文毎に注文明細に金額を集計するなどというのもよくある計算だろう。

そこで、NorthWindsデータベースのorderテーブルとorder_detailテーブルを例に金額集計してみた。

        //明細集計処理
public void linqSumDetails(Form1 form)
{
using (NorthWindDataContext nwnd = new NorthWindDataContext())
{
var sumprice = nwnd.Order.Select(n => new
{
n.OrderID,
price = n.Order_Details.Sum(d =>
d.UnitPrice * d.Quantity)
});
form.dataGridView1.DataSource = sumprice;
}
}

OrderからOrder_detailを手繰って、その単価と数量を掛けた値を合計している。
結果はこんな感じになる。
20110406201936


ちなみに、Discountは考慮していない。

◆LINQで単一集計処理

LINQでは各種単一集計処理用の拡張メソッドが用意されている。(埋め込みクエリー方式には無いようだ)

使い方は特に難しいところは無い。Count以外ではラムダ式で計算対象となるプロパティを指定する。

        //単一集計処理
public void linqSingleSum(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
int salesCount = pubs.sales.Count();
int salesSum = pubs.sales.Where(s => s.payterms == "NET 60")
.Sum(s => s.qty);
int salesMax = pubs.sales.Max(s => s.qty);
string result =
string.Format("Count={0},Net60Sum={1},MaxQty={2}", salesCount, salesSum, salesMax);
MessageBox.Show(result,"単一集計処理結果");

}
}

◆LINQでのソート処理(OrderBy,ThenBy)

LINQでのソート処理はOrderByメソッドで行うことができる。
ソート条件が2つ以上ある時は、2つめ以降にはThenByメソッドを使うらしい。(OrderByをつなげて指定することもできるがその場合は指定する順番が逆になるとの事)
降順の時はそれぞれOrderByDescending、ThenByDescendingとなる。

        //Sort処理
public void linqOrderBy(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var employees = pubs.employee.OrderBy(e => e.job_lvl)
.ThenBy(e => e.lname)
.Select(e => new
{
e.job_lvl,
e.lname,
e.fname,
e.emp_id
});
form.dataGridView1.DataSource = employees;
}
}

//Sort処理(job_lvlの降順)
public void linqOrderByDescending(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var employees = pubs.employee.OrderByDescending(e => e.job_lvl)
.ThenBy(e => e.lname)
.Select(e => new
{
e.job_lvl,
e.lname,
e.fname,
e.emp_id
});
form.dataGridView1.DataSource = employees;
}
}

埋め込みクエリー方式では、


        //Sort
public void linqOrderBy(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var employees = from e in pubs.employee
orderby e.job_lvl , e.lname
select new
{
e.job_lvl,
e.lname,
e.fname,
e.emp_id
};
form.dataGridView1.DataSource = employees;
Console.Beep();
}
}

//Sort(job_lvl降順)
public void linqOrderByDescending(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var employees = from e in pubs.employee
orderby e.job_lvl descending, e.lname
select new
{
e.job_lvl,
e.lname,
e.fname,
e.emp_id
};
form.dataGridView1.DataSource = employees;
Console.Beep();
}
}


また、ソート条件には計算結果なども指定できるので以下のようなソートも可能だ。


        //名前の長さでソート
public void linqOrderByNameLength(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
var employees = pubs.employee.OrderBy(e => e.lname.Length + e.fname.Length)
.Select(e => new
{
name = e.lname + e.fname,
e.job_id
});
form.dataGridView1.DataSource = employees;
}
}


//job毎の社員数でソート
public void linqOrderByJobsEmployeeCount(Form1 form)
{
using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var jobs = pubs.jobs.OrderBy(j => j.employee.Count)
.Select(j => new
{
employeeCount = j.employee.Count,
j.job_id,
j.job_desc
});
form.dataGridView1.DataSource = jobs;

}
}

job毎の社員数でソートの場合は以下のようなSQLが発行されているようなので件数が増えた場合に性能的にどうなのか検証が必要かもしれない。


SELECT (
SELECT COUNT(*)
FROM [dbo].[employee] AS [t2]
WHERE [t2].[job_id] = [t0].[job_id]
) AS [employeeCount], [t0].[job_id], [t0].[job_desc]
FROM [dbo].[jobs] AS [t0]
ORDER BY (
SELECT COUNT(*)
FROM [dbo].[employee] AS [t1]
WHERE [t1].[job_id] = [t0].[job_id]
)
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

2011年4月5日火曜日

◆LINQでマスターから名称を取得するSelect

PubsデータベースのTitlesテーブルにはPub_Idという外部キー項目があり、それをもとにPublishersテーブルからPub_Nameが取得できる。

20110405203548

マスターやコードテーブルから名称を取ってくるというのはRDBにおいて基本中の基本であり頻出するパターンだ。
SQLで書くならtitlesテーブルとpublishersテーブルをJoinする必要がある。
この程度のJoinなら難しくはないのだが私はいつもSQL本を見ながら書いている。(SQLServerとOracleで書き方が違ったりするので、たまにしかSQLを書かない私にはどうにも覚えられない)

これがLINQを使うと以下のようにいとも簡単に書けるようになる。

            using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var titles = pubs.titles.Where(t => t.price > 10)
.Select(t => new
{
t.title,
t.price,
t.publishers.pub_name
});
form.dataGridView1.DataSource = titles;

}


            using (PubsDataContext pubs = new PubsDataContext())
{
pubs.Log = Console.Out;
var titles = from t in pubs.titles
where t.price > 10
select new
{
t.title,
t.price,
t.publishers.pub_name
};
form.dataGridView1.DataSource = titles;
Console.Beep();
}

発行されたSQLを確認するとちゃんとLEFT OUTER JOINが使われている。


ちなみに、これまで私はなぜかリレーションを張ったDBを使っているシステムを見たことがない。
なので、INNER JOIN したい場合のLINQはどう書けば良いのだろうなどと考えてしまった。

私が最近チェックした記事