none
DataTable.Select("ID = Max(ID)") の結果が2件となる RRS feed

  • 質問

  • 調べても情報を見つけられませんでしたので、この挙動について情報をいただければと思います。

    下記のコードを実行すると、Select() の結果が「ID == 3」の 1 件となることを期待していましたが、実際には「ID == 2」と「ID == 3」の 2 件となります。最大値は明らかに 3 ですが、結果に 3 以外が含まれるこの挙動は当たり前なのでしょうか。

    試した環境
    ・Windows7 x64 Professional / VS2010 Professional / .NET 3.5 / C#
    ・Windows7 x64 Professional / VS2010 Professional / .NET 4.0 / C#
    ・Windows7 x64 Professional / VS11 Professional Beta / .NET 4.5 Beta / C#

    C#のコードです。

    DataTable table = new DataTable();
    table.Columns.Add("ID", typeof(int));
    
    // ID 0 ~ 2 の行を持つデータテーブルを作成
    table.Rows.Add(0);
    table.Rows.Add(1);
    table.Rows.Add(2);
    table.AcceptChanges();
    
    // ID 3 の行を追加
    table.Rows.Add(3);
    
    // ID が最大の行を取得
    DataRow[] rows = table.Select("ID = Max(ID)");
    // ※※ ↑↑この検索処理で、ID == 2 と ID == 3 の2行が返却される。※※
    

    ID 3 を追加する前の AcceptChanges() をコメントアウトするとこの挙動にはなりません。
    ID に主キーを設定しても 2 件ですし、Select() ではなく DataView の RowFilter を使用しても 2 件です。
    また、table.Rows.Add(3); (RowStatus == Added) の代わりに table.Select("ID = 1")[0]["ID"] = 3; (RowStatus == Modified) としても 2 件となります。

    DataTable.Select メソッドのソースコードを見たところ、複数の RowStatus が混在している場合にこの挙動になることがあるように見えます。

    このような挙動になっている理由、
    Select() で集計関数を使うべきでは無い(RowStatus が Added、Unchanged、Modified のいずれかで統一されている時だけ使える?)など、
    情報がありましたらお願いします。

    前回同様、DataTable の使い方を間違えている可能性はあるので、上記コードに問題があればその指摘もお願いします。

    2012年5月7日 16:50

回答

  • 確かにおっしゃるようになりますね。
    すいません、なんでこうなるのか、はわかりませんが、
    DataTable.Select()メソッドは行の状態も引数として
    受け取るのでそのためかなあとは思いました。

    以下は興味があったので試してみた結果です。

    DataTable.Compute()だとどうなるかな、と思いましたが
    特に問題なく 3 が返ってきます。

    string max_id = table.Compute("MAX(ID)", "").ToString();
    // max_id = 3

    なのでこうしてしまえばいつでも最大値で取れるのですね。
    DataRow[] rows = table.Select(String.Format("ID = {0}", max_id));

    # DataTable.SelectのexpressionってSQL文のWHERE句と同じじゃなかったんですね。
    # (SQL文だと "ID = Max(ID)" はエラーになる)
    # 初めて知りました。

    ※ 追記

    // ID 0 ~ 2 の行を持つデータテーブルを作成
    table.Rows.Add(5, "a");
    table.Rows.Add(6, "b");
    table.Rows.Add(7, "c");
    table.AcceptChanges();
    
    // ID 3 の行を追加
    table.Rows.Add(3, "d");
    
    // ID が最大の行を取得
    DataRow[] rows = table.Select("ID = MAX(ID)", "");

    こんなふうにしたときはID=7の行が1行だけとれました。謎ですね。

    • 編集済み mars12 2012年5月8日 2:46 新たに試してみたのを追記
    • 回答としてマーク hpyamada 2012年5月8日 13:00
    2012年5月8日 0:48
  • 私もいろいろ試してみましたが、挙動に一貫性がないように思います。Microsoft Connectで報告した方が良いかもしれません。
    ちなみに現在ではLINQで抽出することが多いので、DataTableのSelectメソッドはあまり使わなくなりました。
    LINQで抽出したり、DefaultViewのSortメソッドでソートして一番最初の行を取り出す方法では問題ないようでした。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク hpyamada 2012年5月8日 13:00
    2012年5月8日 4:16
    モデレータ

すべての返信

  • 確かにおっしゃるようになりますね。
    すいません、なんでこうなるのか、はわかりませんが、
    DataTable.Select()メソッドは行の状態も引数として
    受け取るのでそのためかなあとは思いました。

    以下は興味があったので試してみた結果です。

    DataTable.Compute()だとどうなるかな、と思いましたが
    特に問題なく 3 が返ってきます。

    string max_id = table.Compute("MAX(ID)", "").ToString();
    // max_id = 3

    なのでこうしてしまえばいつでも最大値で取れるのですね。
    DataRow[] rows = table.Select(String.Format("ID = {0}", max_id));

    # DataTable.SelectのexpressionってSQL文のWHERE句と同じじゃなかったんですね。
    # (SQL文だと "ID = Max(ID)" はエラーになる)
    # 初めて知りました。

    ※ 追記

    // ID 0 ~ 2 の行を持つデータテーブルを作成
    table.Rows.Add(5, "a");
    table.Rows.Add(6, "b");
    table.Rows.Add(7, "c");
    table.AcceptChanges();
    
    // ID 3 の行を追加
    table.Rows.Add(3, "d");
    
    // ID が最大の行を取得
    DataRow[] rows = table.Select("ID = MAX(ID)", "");

    こんなふうにしたときはID=7の行が1行だけとれました。謎ですね。

    • 編集済み mars12 2012年5月8日 2:46 新たに試してみたのを追記
    • 回答としてマーク hpyamada 2012年5月8日 13:00
    2012年5月8日 0:48
  • 私もいろいろ試してみましたが、挙動に一貫性がないように思います。Microsoft Connectで報告した方が良いかもしれません。
    ちなみに現在ではLINQで抽出することが多いので、DataTableのSelectメソッドはあまり使わなくなりました。
    LINQで抽出したり、DefaultViewのSortメソッドでソートして一番最初の行を取り出す方法では問題ないようでした。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク hpyamada 2012年5月8日 13:00
    2012年5月8日 4:16
    モデレータ
  • 推測交じりですが、
    確定済みデータ(AcceptChanges済)の判定と未確定のデータの判定を個別に行うのだと思います。

    そう考えた場合今回のケースは下記になります。

    1.確定済みデータは0, 1, 2なのでその中ではMax(ID)=2の為、2の行が取得される。
    2.未確定のデータは3なのでMax(ID)=3の為、3の行が取得される。

    結果として、RowStateがUnchangedのID=2の行と、RowStateがAddedのID=3の行が取得される。

    こういう事だと思います。

    ちなみに、下記の様にしても同様の結果(RowStateがUnchangedのID=2の行と、RowStateがAddedのID=3の行が取得される)でした。

    DataTable table = new DataTable();
    table.Columns.Add("ID", typeof(int));
    // ID 0 ~ 2 の行を持つデータテーブルを作成
    table.Rows.Add(0);
    table.Rows.Add(1);
    table.Rows.Add(2);
    table.AcceptChanges();
    table.Rows.Add(2); // 未確定のデータとしてID=2を追加
    // ID 3 の行を追加
    table.Rows.Add(3);
    // ID が最大の行を取得
    DataRow[] rows = table.Select("ID = Max(ID)");
    2012年5月8日 4:53
  • 気になったので追加で検証してみました。

    DataTable table = new DataTable();
    table.Columns.Add("ID", typeof(int));
    
    // ID 0 ~ 2 の行を持つデータテーブルを作成
    DataRow row0 = table.NewRow();
    DataRow row1 = table.NewRow();
    DataRow row2 = table.NewRow();
    row0["ID"] = 0;
    row1["ID"] = 1;
    row2["ID"] = 2;
    table.Rows.Add(row0);
    table.Rows.Add(row1);
    table.Rows.Add(row2);
    table.AcceptChanges();
    
    // ID 2 の行に ID 2 をセット(値は変わらないが、RowStateがModifiedになる)
    row2["ID"] = 2;
    
    // ID 3 の行を追加
    table.Rows.Add(3);
    
    // ID が最大の行を取得
    DataRow[] rows = table.Select("ID = Max(ID)");

    これを実行すると、取得されるのはRowStateがAddedのID=3の行のみでした。
    ちなみに、「row2["ID"] = 3;」とした場合にはRowStateがModifiedとAddedのID=3の2行が取得されました。

    これからも、確定済データと未確定データは別に取得されてる様に感じました。
    ※というか、RowState(DataViewRowState)毎に取得されてるのではないかと思います。

    2012年5月8日 5:08
  • mars12 様、返信ありがとうございます。

    おっしゃるとおり、DataTable.Compute メソッドだと期待する値が戻ってきます。
    集計関数は Compute メソッドで使用するほうが安全のようですね。

    mars12 様の追記についてですが、Max() を使う場合、Added や Modified の行が最大値を持っているとこの挙動となります。
    Min() を使う場合、Added や Modified の行が最小値を持っているとこの挙動になります。
    また普通では無い実装ですが、COUNT() を使ってもこの挙動になります。
    どの集計関数でも起きるかもしれません。


    • 編集済み hpyamada 2012年5月8日 5:20
    2012年5月8日 5:11
  • trapmiya 様、返信ありがとうございます。

    なるほど、LINQ も使えますね。
    DefaultView の Sort は問題無いとのことで試したところ、Select メソッドの sort 引数でも問題ないようです。

    Microsoft Connect には、もう少し様子を見てから報告しようと思います。アドバイスありがとうございます。

    2012年5月8日 5:37
  • aviator__ 様、返信ありがとうございます。

    よく理解していないだけかもしれませんが、確定済データと未確定データを別に取得しているなら、追加で検証されたコードは Unchanged の ID = 1、Added の ID = 3 の2件になるのではないでしょうか。間違えていたらごめんなさい。

    ただ、私の思っていた結果とも違いました。
    やはり trapmiya 様のおっしゃる通り一貫性が無いように思います。

    2012年5月8日 12:59
  • 返信になってなくて申し訳ありませんが、忘れさっていた調査内容を少し思い出したので・・・。

    Select メソッドで集約構文を使わなくなっていたので忘れていたのですが、以前に Select の動作が思うようにいかず調べた時に aviator__ さんの意見通りの結論になった記憶があります。

    1割くらいは「Select メソッドでの Expression 式の集約構文は DataViewRowState 状態毎にグループ化される」仕様という可能性もありえますが、「DataColumn クラスの Expression プロパティ値と同規則」の記述があるので9割方バグでしょうから Connect 報告するのが良いと思います。

    ※試してないため確信できてないのですが、一貫性あるようにみえますが、ありませんかね?

    2012年5月9日 4:40
  • kyano30 様、返信ありがとうございます。

    遅くなりましたが、Connect に報告しました。
    こちらにリンクを張っておきます。
    DataTable.Select() にて集約関数を使用すると、期待しない DataRow が返却される

    皆様、様々な検証や助言をしてくださり、ありがとうございました。
    • 編集済み hpyamada 2012年5月13日 14:30
    2012年5月13日 14:24