none
DataTableのコピー RRS feed

  • 質問

  • お世話になります。

    まずやりたいことの説明ですが、

    取得したDataTableの各レコードの値によって、列を選択してコピーしたと思っています。

      |C1|C2| | C3  |

    1:|0  |AAA|BBB|
    2:|1  |AAA|BBB|
    3:|2  |AAA|BBB|

    コピー先は、C1が「0」なら「C2の値」、「1」なら「C3の値」、「2」なら両方の値をもとに対象に2レコードが作成されたいと思っています。

      |C1|C4 | 
    1:|0  |AAA|
    2:|1  |BBB|
    3:|2  |AAA|
    4:|2  |BBB|

    行単位で、列を選択したコピーが可能でしょうか。

    また列名が異なるテーブルへのコピー方法があれば教えてください。

    2013年7月4日 2:33

回答

  • SQLはもうちょっと簡単に書けるのでは?
    SELECT C1, C2 AS C4
    FROM tableA
    WHERE C1=0 OR C1=2
    UNION ALL
    SELECT C1, C3
    FROM tableA
    WHERE C1=1 OR C1=2

    2013年7月4日 4:07
  • コードを書きました。検証済みです。

    var items = dt.AsEnumerable()
                    .Where(p => p.Field<int>("C1") == 0 || p.Field<int>("C1") == 2)
                    .Select(p => new
                                {
                                    C1 = p.Field<int>("C1"),
                                    C4 = p.Field<string>("C2")
                                }
                            )
            .Concat        //コピー先を一意にしたい場合は、Unionにして下さい。
            (
                dt.AsEnumerable()
                    .Where(p => p.Field<int>("C1") == 1 || p.Field<int>("C1") == 2)
                    .Select(p => new
                                {
                                    C1 = p.Field<int>("C1"),
                                    C4 = p.Field<string>("C3")
                                }
                            )
            );
    
    var dt2 = new DataTable();
    dt2.Columns.Add("C1", typeof(int));
    dt2.Columns.Add("C4", typeof(string));
    
    foreach (var item in items)
    {
        dt2.Rows.Add(new object[]{item.C1, item.C4});
    }
     

    #ひらぽんさん。タイポだと思いますが、2番目のWhereのところのp.Field(Of Int32)("C1") = 0 は、1の誤りですよね。


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


    • 編集済み trapemiyaModerator 2013年7月5日 1:00 コード部分の再掲載
    • 回答としてマーク Brillia 2013年7月5日 9:46
    2013年7月5日 0:53
    モデレータ

すべての返信

  • もう既にコピーでも何でもなく新しいテーブルを作っているように見えます。普通にforeachで各行を処理していけばいいんじゃないでしょうか。

    // DBにあるのがTABLE1で、DataTableに持ってくる際にTABLE2の形に置き換えるというのならともかく。

    2013年7月4日 2:55
  • sqlで解決する手も有るとは思います、、、カラムが多いとたいへんですが、2,3個位なら、 select c1, case c1 when 1 then c2 when 2 then c3 when 3 then c2 end as c4 from tableA union all select c1, c3 as c4 from tableA where c1 = 3

    jzkey

    2013年7月4日 3:45
  • SQLはもうちょっと簡単に書けるのでは?
    SELECT C1, C2 AS C4
    FROM tableA
    WHERE C1=0 OR C1=2
    UNION ALL
    SELECT C1, C3
    FROM tableA
    WHERE C1=1 OR C1=2

    2013年7月4日 4:07
  • Linqを使って考えてみましたが、もっと良いコードがあるかもしれません。

    var items = dt.AsEnumerable().Where(p => p.Field<int>("C1") == 0 || p.Field<int>("C1") == 1)
                .Select(p =>
                    new
                    {
                        C1 = p.Field<int>("C1"),
                        C4 = p.Field<int>("C1") == 0 ? p.Field<string>("C2") : p.Field<string>("C3")
                    }
                    )
                .Union
    
                (
                dt.AsEnumerable().Where(p => p.Field<int>("C1") == 2)
                .Select(p =>
                    new
                    {
                        C1 = p.Field<int>("C1"),
                        C4 = p.Field<string>("C2")
                    }
                    )
                )
                .Union
                (
                dt.AsEnumerable().Where(p => p.Field<int>("C1") == 2)
                .Select(p =>
                    new
                    {
                        C1 = p.Field<int>("C1"),
                        C4 = p.Field<string>("C3")
                    })
                );
    
    var dt2 = new DataTable();
    dt2.Columns.Add("C1", typeof(int));
    dt2.Columns.Add("C4", typeof(string));
    
    foreach (var item in items)
    {
        var row = dt2.NewRow();
        row["C1"] = item.C1;
        row["C4"] = item.C4;
    
        dt2.Rows.Add(row);
    }


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

    2013年7月4日 7:25
    モデレータ
  • trapemiyaさんが LINQ での解を考えてくださったので VB 版も考えてみました。お役に立てるか判りませんが参考までにどうぞ。

    Dim items =
            (From p In dt.AsEnumerable() Where
                            p.Field(Of Int32)("C1") = 0 OrElse p.Field(Of Int32)("C1") = 1
                    Select
                            New With {
                                    .C1 = p.Field(Of Int32)("C1"),
                                    .C4 = If(p.Field(Of Int32)("C1") = 0,
                                                     p.Field(Of String)("C2"), p.Field(Of String)("C3"))
                            }
            ).Union(
                    From p In dt.AsEnumerable() Where p.Field(Of Int32)("C1") = 2
                    Select
                            New With {
                                    .C1 = p.Field(Of Int32)("C1"),
                                    .C4 = p.Field(Of String)("C2")
                            }
            ).Union(
                    From p In dt.AsEnumerable() Where p.Field(Of Int32)("C1") = 2
                    Select
                            New With {
                                    .C1 = p.Field(Of Int32)("C1"),
                                    .C4 = p.Field(Of String)("C3")
                            }
            )
    Dim dt2 = New DataTable()
    dt2.Columns.Add("C1", GetType(Int32))
    dt2.Columns.Add("C4", GetType(String))
    
    For Each item In items
            dt2.Rows.Add(New Object() {item.C1, item.C4})
    Next


    ひらぽん http://d.hatena.ne.jp/hilapon/


    • 編集済み ひらぽんModerator 2013年7月4日 8:49 #VB だとメソッド構文があまりに冗長すぎるので、クエリ構文に変えました
    2013年7月4日 8:31
    モデレータ
  • trapemiyaさん、ひらぽんさんへ

    LINQのUnion()は重複が消えるので、Concat()ですよ。

    ところで3名揃ってなんでそんな複雑なクエリ式になるのか不思議なのですが…。

    2013年7月4日 13:05
  • LINQのUnion()は重複が消えるので、Concat()ですよ。

    SQLのUnionもそうですね。うっかりしていました。ご指摘、ありがとうございます。

    ところで3名揃ってなんでそんな複雑なクエリ式になるのか不思議なのですが…。

    佐祐理さんが先に投稿されたSQLを、今読みました(すみません)。確かに、それでいけそうですね。(union allのC3はas C4が要ると思いますが)
    私の書いたLinqは、仕様の説明の通りをそのまま直訳したような形になっていますので、スマートとは言えませんね。
    ただ、私も自分の勉強を兼ねて、そういう形にこだわっていました。それが、盲目的になったようです。
    ソリューションとしては、佐祐理さんが書かれているSQLのようなLinqがスマートでベストだと思います。明日にでもそのコードを書いてみます。

    #佐祐理さんのするどい突込みは、刺激になり、勉強になります。ありがとうございます。


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

    2013年7月4日 14:22
    モデレータ
  • trapemiyaさん、ひらぽんさんへ

    LINQのUnion()は重複が消えるので、Concat()ですよ。

    ところで3名揃ってなんでそんな複雑なクエリ式になるのか不思議なのですが…。

    いま読み返しました。まったくもって恥ずかしい限りです(大汗

    佐佑理さんの回答を基にするとこんな感じになりますかね。

    Dim items =
        (From p In dt.AsEnumerable() Where
                        p.Field(Of Int32)("C1") = 0 OrElse p.Field(Of Int32)("C1") = 2
                Select
                        New With {
                                .C1 = p.Field(Of Int32)("C1"),
                                .C4 = p.Field(Of String)("C2")
                        }
        ).Concat(
                From p In dt.AsEnumerable() Where
                        p.Field(Of Int32)("C1") = 1 OrElse p.Field(Of Int32)("C1") = 2
                Select
                        New With {
                                .C1 = p.Field(Of Int32)("C1"),
                                .C4 = p.Field(Of String)("C3")
                        }
        )


    ひらぽん http://d.hatena.ne.jp/hilapon/


    • 編集済み ひらぽんModerator 2013年7月5日 1:14 はうぅ。。0 から 1 に直しときましたぁ(汗
    2013年7月4日 14:56
    モデレータ
  • コードを書きました。検証済みです。

    var items = dt.AsEnumerable()
                    .Where(p => p.Field<int>("C1") == 0 || p.Field<int>("C1") == 2)
                    .Select(p => new
                                {
                                    C1 = p.Field<int>("C1"),
                                    C4 = p.Field<string>("C2")
                                }
                            )
            .Concat        //コピー先を一意にしたい場合は、Unionにして下さい。
            (
                dt.AsEnumerable()
                    .Where(p => p.Field<int>("C1") == 1 || p.Field<int>("C1") == 2)
                    .Select(p => new
                                {
                                    C1 = p.Field<int>("C1"),
                                    C4 = p.Field<string>("C3")
                                }
                            )
            );
    
    var dt2 = new DataTable();
    dt2.Columns.Add("C1", typeof(int));
    dt2.Columns.Add("C4", typeof(string));
    
    foreach (var item in items)
    {
        dt2.Rows.Add(new object[]{item.C1, item.C4});
    }
     

    #ひらぽんさん。タイポだと思いますが、2番目のWhereのところのp.Field(Of Int32)("C1") = 0 は、1の誤りですよね。


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


    • 編集済み trapemiyaModerator 2013年7月5日 1:00 コード部分の再掲載
    • 回答としてマーク Brillia 2013年7月5日 9:46
    2013年7月5日 0:53
    モデレータ
  • 皆様、ありがとうございました。

    LINQ勉強します。

    2013年7月5日 9:45
  • > LINQ勉強します。

    LINQ など使わなくでもできますよ。LINQ を使った回答は凝りすぎ(決して初心者向けではないという意味)です。

    2013年7月5日 11:11
  • > LINQ勉強します。

    私も LINQ で返信しましたが、SurferOnWww さんが言われるとおり、今回のケースなら佐祐理さんが提示した SQL で充分だと思います。

    LINQ の回答も佐祐理さんの返信を基にしてますので、私の方で佐祐理さんの返信に「回答マーク」を付けさせて頂きました。

    もっとも LINQ の勉強は決して無駄にならないので、これを機会に併せて勉強しとくのもいいでしょう。


    ひらぽん http://d.hatena.ne.jp/hilapon/


    2013年7月5日 11:34
    モデレータ
  • > LINQ勉強します。

    Hongliangさんの1行ずつ見ながら処理していく方法、 jzkeyさん、佐祐理さんのSQLの方法が出ていたので、3番目の方法として、私の勉強も兼ねて、LINQによる方法をご紹介しました。
    SQLで処理できるのであればSQLで処理を行うのが良いと思います。そうではなく、データテーブルを元にせざるを得ないのであれば、Hongliangさんの方法が基本だと思います。まだ、プログラマーとして経験が浅いのであれば、まずはHongliangさんの方法を試されるべきです。そういう逐次型の処理はプログラミングの基本になります。また、ADO.NETの基本を知ることにもなります。

    LINQを知らなくてもプログラムは組めますが、身に付けておけば、短く、かつバグの発生しにくいコードを書くことが可能になります。例えば手の爪はハサミでも切ることができますが、爪切りがあればより簡単にきれに切ることができます。プログラミングにおいても、このような道具を揃えていくことが、自分を高めることになります。
    LINQを今すぐに勉強して、LINQで処理をしなさいと言っているのではありません。LINQという道具を使うとこんな風になりますよ、と、LINQという道具を紹介したかったのです。ちょっとコケましたけどね・・・


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

    2013年7月5日 14:02
    モデレータ