從 DataTable 擷取某欄位的不重複值,建立新 DataTable 供下拉選單、報表分組、主從關聯使用。ADO.NET 無內建 SELECT DISTINCT,需自行實作。
using System;
using System.Data;
public class DataSetHelper
{
public DataSet DataSet { get; private set; }
public DataSetHelper(DataSet ds = null)
{
DataSet = ds;
}
///
/// 比較兩值是否相等,正確處理 DBNull.Value
///
private bool ColumnEqual(object a, object b)
{
if (a == DBNull.Value && b == DBNull.Value) return true;
if (a == DBNull.Value || b == DBNull.Value) return false;
return a.Equals(b);
}
///
/// 從來源 DataTable 指定欄位建立去重 DataTable
///
/// 新資料表名稱
/// 來源資料表
/// 去重欄位名稱
/// 去重後的 DataTable
public DataTable SelectDistinct(string tableName, DataTable sourceTable, string fieldName)
{
if (sourceTable == null) throw new ArgumentNullException(nameof(sourceTable));
if (!sourceTable.Columns.Contains(fieldName))
throw new ArgumentException($"欄位 '{fieldName}' 不存在");
DataTable distinctTable = new DataTable(tableName);
distinctTable.Columns.Add(fieldName, sourceTable.Columns[fieldName].DataType);
// 使用 Select 排序後遍歷,相鄰比較去重
object lastValue = null;
foreach (DataRow row in sourceTable.Select("", fieldName))
{
object currentValue = row[fieldName];
if (lastValue == null || !ColumnEqual(lastValue, currentValue))
{
lastValue = currentValue;
distinctTable.Rows.Add(new object[] { currentValue });
}
}
// 選擇性加入 DataSet
if (DataSet != null)
DataSet.Tables.Add(distinctTable);
return distinctTable;
}
}
using System;
using System.Data;
using System.Windows.Forms;
public class DistinctDemoForm : Form
{
private DataGridView grid;
private Button btnDistinct;
private DataSet ds;
private DataSetHelper helper;
public DistinctDemoForm()
{
grid = new DataGridView { Dock = DockStyle.Fill, ReadOnly = true };
btnDistinct = new Button { Text = "取得不重複員工", Dock = DockStyle.Bottom, Height = 40 };
btnDistinct.Click += BtnDistinct_Click;
Controls.Add(grid);
Controls.Add(btnDistinct);
Text = "DataTable Distinct Demo";
ClientSize = new Size(600, 400);
InitializeData();
}
private void InitializeData()
{
ds = new DataSet();
helper = new DataSetHelper(ds);
// 建立訂單資料表
DataTable orders = new DataTable("Orders");
orders.Columns.Add("EmployeeID", typeof(string));
orders.Columns.Add("OrderID", typeof(int));
orders.Columns.Add("Amount", typeof(decimal));
// 測試資料:多筆重複員工
orders.Rows.Add("Sam", 5, 25.00m);
orders.Rows.Add("Tom", 7, 50.00m);
orders.Rows.Add("Sue", 9, 11.00m);
orders.Rows.Add("Tom", 12, 7.00m);
orders.Rows.Add("Sam", 14, 512.00m);
orders.Rows.Add("Sue", 15, 17.00m);
orders.Rows.Add("Sue", 22, 2.50m);
orders.Rows.Add("Tom", 24, 3.00m);
orders.Rows.Add("Tom", 33, 78.75m);
ds.Tables.Add(orders);
grid.DataSource = orders;
}
private void BtnDistinct_Click(object sender, EventArgs e)
{
// 只能點一次,避免重複加入同名表
btnDistinct.Enabled = false;
DataTable distinctEmployees = helper.SelectDistinct(
"DistinctEmployees", ds.Tables["Orders"], "EmployeeID");
grid.DataSource = distinctEmployees;
Text = $"不重複員工數:{distinctEmployees.Rows.Count}";
}
[STAThread]
static void Main() => Application.Run(new DistinctDemoForm());
}
public DataTable SelectDistinct(string tableName, DataTable sourceTable, params string[] fieldNames)
{
DataTable distinctTable = new DataTable(tableName);
foreach (string fn in fieldNames)
distinctTable.Columns.Add(fn, sourceTable.Columns[fn].DataType);
// 建立複合鍵比較
var seenKeys = new HashSet();
foreach (DataRow row in sourceTable.Select("", string.Join(",", fieldNames)))
{
object[] keyValues = new object[fieldNames.Length];
for (int i = 0; i < fieldNames.Length; i++)
keyValues[i] = row[fieldNames[i]];
string compositeKey = string.Join("|", keyValues.Select(v =>
v == DBNull.Value ? "NULL" : v.ToString()));
if (seenKeys.Add(compositeKey)) // Add 回傳 true 表示新鍵
distinctTable.Rows.Add(keyValues);
}
if (DataSet != null) DataSet.Tables.Add(distinctTable);
return distinctTable;
}
| 方法 | 優點 | 缺點 | 適用版本 |
|---|---|---|---|
| DataSetHelper.SelectDistinct | 無相依、可控制邏輯、處理 DBNull | 需自行維護 | 所有版本 |
DataView.ToTable(true, "col") | 內建、一行程式碼 | 不支援多欄位、排序行為固定 | .NET 2.0+ |
LINQ: table.AsEnumerable().Select(r=>r.Field<T>("col")).Distinct() | 語法簡潔、強型別、可轉回 DataTable | 需 .NET 3.5+、效能略低 | .NET 3.5+ |
SQL SELECT DISTINCT | 資料庫層面去重、效能最佳 | 需資料庫支援、不適用離線 DataSet | 所有版本 |
// 最簡單的一行去重
DataTable distinct = sourceTable.DefaultView.ToTable(true, "EmployeeID");
// true = distinct, 後接欄位名稱陣列
DataTable distinctMulti = sourceTable.DefaultView.ToTable(true, "EmployeeID", "Department");
SelectDistinct 核心:排序 + 相鄰比較 + DBNull.Value 正確處理DataSetHelper 封裝邏輯,可選擇性加入 DataSet 管理DataView.ToTable(true, ...) 或 LINQHashSet 效能優於雙重迴圈SELECT DISTINCT) 完成