C# における DataTable の主要な操作手法

DataTable の結合処理

複数の DataTable を統合する場景は頻繁に発生します。スキーマ(列構造)が一致している場合と異なっている場合で、アプローチが異なります。

1. スキーマが同一の場合

両方のテーブルが同じ列定義を持つ場合、データを単純に追加していくことで結合が可能です。

まず、検証用のデータを準備するメソッドを定義します。

private void SetupSampleTableA(DataTable table)
{
    table.Columns.Add("employee_id");
    table.Columns.Add("employee_name");
    table.Rows.Add("E001", "Tanaka");
    table.Rows.Add("E002", "Suzuki");
    table.Rows.Add("E003", "Sato");
}

private void SetupSampleTableB(DataTable table)
{
    table.Columns.Add("employee_id");
    table.Columns.Add("employee_name");
    table.Rows.Add("E101", "Yamada");
    table.Rows.Add("E102", "Ito");
    table.Rows.Add("E103", "Watanabe");
}

結合にはいくつかのアプローチがあります。一つ目は、構造のみをクローンし、行データを配列経由で追加する方法です。

var mergedTable = tableA.Clone();
var buffer = new object[mergedTable.Columns.Count];

// 表 A のデータ追加
foreach (DataRow row in tableA.Rows)
{
    row.ItemArray.CopyTo(buffer, 0);
    mergedTable.Rows.Add(buffer);
}

// 表 B のデータ追加
foreach (DataRow row in tableB.Rows)
{
    row.ItemArray.CopyTo(buffer, 0);
    mergedTable.Rows.Add(buffer);
}

二つ目は、Copy メソッドで構造とデータを複製し、ImportRow を使用して追加する方法です。こちらの方が簡潔です。

var mergedTable = tableA.Copy();

foreach (DataRow row in tableB.Rows)
{
    mergedTable.ImportRow(row);
}

2. スキーマが異なる場合

列構造が異なる場合、横方向に列を拡張しながらデータを結合する必要があります。行数が一致しない場合の考慮も必要です。

private void SetupDifferentSchemaTableA(DataTable table)
{
    table.Columns.Add("id_group_a");
    table.Columns.Add("name_group_a");
    table.Rows.Add("001", "Alice");
    table.Rows.Add("002", "Bob");
}

private void SetupDifferentSchemaTableB(DataTable table)
{
    table.Columns.Add("id_group_b");
    table.Columns.Add("name_group_b");
    table.Rows.Add("111", "Charlie");
    table.Rows.Add("222", "David");
    table.Rows.Add("333", "Eve");
}

以下のメソッドは、最初の表をベースにし、二つ目の表の列とデータを横に接続します。

private DataTable MergeHorizontal(DataTable sourceLeft, DataTable sourceRight, string tableName)
{
    var resultTable = sourceLeft.Clone();

    // 右側の表の列構造を追加
    foreach (DataColumn col in sourceRight.Columns)
    {
        resultTable.Columns.Add(col.ColumnName, col.DataType);
    }

    // 左側のデータをコピー
    foreach (DataRow row in sourceLeft.Rows)
    {
        resultTable.Rows.Add(row.ItemArray);
    }

    // 右側のデータを対応する行に埋め込む
    int maxRows = Math.Max(sourceLeft.Rows.Count, sourceRight.Rows.Count);
    
    // 行数が少ない場合に備えて行を追加
    for (int i = sourceLeft.Rows.Count; i < maxRows; i++)
    {
        resultTable.Rows.Add(resultTable.NewRow());
    }

    // 右側データの値をセット
    for (int i = 0; i < sourceRight.Rows.Count; i++)
    {
        for (int j = 0; j < sourceRight.Columns.Count; j++)
        {
            int targetColIndex = sourceLeft.Columns.Count + j;
            resultTable.Rows[i][targetColIndex] = sourceRight.Rows[i][j];
        }
    }

    resultTable.TableName = tableName;
    return resultTable;
}

DataTable の比較処理

テーブル間の差分や共通データを抽出するには、LINQ を活用するのが効率的です。

完全一致による比較

構造が完全に一致している場合、Except や Intersect を直接使用できます。

// 除外リストに含まれないユーザーを抽出
var activeUsers = masterTable.AsEnumerable()
    .Except(excludeTable.AsEnumerable(), DataRowComparer.Default);

// 両方に存在するユーザーを抽出
var commonUsers = powerUserTable.AsEnumerable()
    .Intersect(activeUsers, DataRowComparer.Default);

var userList = new List<string>();
foreach (var row in commonUsers)
{
    userList.Add(row.Field<string>("username"));
}

特定列による比較

一部の列のみをキーとして比較を行う場合は、Where 句と Contains を組み合わせます。

// 除外リストに用户名が含まれていないレコードを筛选
var validRecords = from row in masterTable.AsEnumerable()
                   where !excludeTable.AsEnumerable()
                       .Select(r => r.Field<string>("username"))
                       .Contains(row.Field<string>("username"))
                   select row;

// 一致するレコードからトークンを取得
var tokens = from row in validRecords
             where powerUserTable.AsEnumerable()
                 .Select(r => r.Field<string>("username"))
                 .Contains(row.Field<string>("username"))
             select row.Field<string>("token");

行と列の抽出操作

DataView を利用することで、元のテーブルを変更せずにフィルタリングや列の選択が可能です。

行のフィルタリング

RowFilter プロパティを使用して条件に合致する行のみを取得します。

DataView view = new DataView(sourceTable);
view.RowFilter = "Status = 'Active' AND Age > 20";
DataTable filteredTable = view.ToTable();

特定の列のみを抽出

ToTable メソッドの引数に列名を指定することで、必要な列のみを持つ新しいテーブルを作成できます。

DataTable subsetTable = sourceTable.DefaultView.ToTable(
    false, 
    new string[] { "UserId", "UserName", "Email" }
);

列の順序変更と名前変更

列のインデックス順序を変更するには SetOrdinal を使用します。列名の変更は ColumnName プロパティで行います。

DataTable table = new DataTable();
table.Columns.Add("ColA");
table.Columns.Add("ColB");
table.Columns.Add("ColC");

// 3 列目を先頭に移動
table.Columns["ColC"].SetOrdinal(0);

// 列名の改名
table.Columns["ColA"].ColumnName = "Identifier";

新規列の追加と初期化

列を追加する際、DefaultValue を設定することで、既存の行に対する値の初期化を自動で行うことができます。

DataColumn countCol = new DataColumn();
countCol.ColumnName = "ProcessCount";
countCol.DataType = typeof(int);
countCol.DefaultValue = 0;

sourceTable.Columns.Add(countCol);

タグ: C# ado.net datatable LINQ system.data

6月23日 22:36 投稿