SSIS脚本任务中基于Test File Name字段去重并保留最早记录
SSIS脚本任务C#处理CSV去重:按Test File Name保留排序后第一条记录
针对你的需求,结合你熟悉SQL的背景,下面是一套在SSIS脚本任务中可用的C#实现方案,核心思路和SQL里「先排序再取每组TOP 1」的逻辑一致:
核心思路
- 用
TextFieldParser读取CSV(比直接读行更可靠,能处理带引号的字段) - 将每行数据映射为实体类,方便后续Linq操作(对应SQL里的表结构)
- 按
Date Time字段排序(对应SQL的ORDER BY Date Time) - 按
Test File Name分组,取每组第一条记录(对应SQL的ROW_NUMBER() OVER(PARTITION BY Test File Name ORDER BY Date Time) = 1) - 将结果写入新CSV文件
完整代码实现
在SSIS中添加脚本任务,选择C#作为脚本语言,替换Main方法内的代码如下:
using System; using System.IO; using System.Linq; using Microsoft.VisualBasic.FileIO; using System.Globalization; namespace ST_XXXXXXXXX // 保留脚本任务自动生成的命名空间即可 { public class ScriptMain : UserComponent { public override void Main() { // 替换为你的源CSV路径和目标CSV路径,也可以用SSIS变量传递(见下方说明) string sourceCsvPath = @"C:\YourSourceFile.csv"; string targetCsvPath = @"C:\YourTargetFile.csv"; // 定义CSV行的实体类,根据你的实际列名/类型调整 public class CsvRecord { public string TestFileName { get; set; } public DateTime DateTime { get; set; } // 添加CSV中的其他列示例: // public string DeviceID { get; set; } // public decimal ResultValue { get; set; } } try { var csvRecords = new System.Collections.Generic.List<CsvRecord>(); using (TextFieldParser parser = new TextFieldParser(sourceCsvPath)) { parser.TextFieldType = FieldType.Delimited; parser.SetDelimiters(","); // 若CSV用其他分隔符(如制表符\t),此处修改 parser.HasFieldsEnclosedInQuotes = true; // 处理带引号的字段 // 读取并跳过表头(如需复用表头可保存后写入目标文件) string[] headers = parser.ReadFields(); // 逐行读取解析数据 while (!parser.EndOfData) { string[] fields = parser.ReadFields(); if (fields == null || fields.Length < 2) continue; // 跳过无效行 // 解析日期:格式需与CSV中Date Time字段完全匹配,如"MM/dd/yyyy hh:mm tt" if (DateTime.TryParseExact(fields[1], "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) { csvRecords.Add(new CsvRecord { TestFileName = fields[0], // 假设Test File Name是第1列(索引0) DateTime = dateTime, // 映射其他列示例: // DeviceID = fields[2], // ResultValue = decimal.Parse(fields[3]) }); } } } // 先按Date Time升序排序,再按TestFileName分组取第一条(取最新则用OrderByDescending) var uniqueRecords = csvRecords .OrderBy(r => r.DateTime) .GroupBy(r => r.TestFileName) .Select(g => g.First()) .ToList(); // 写入新CSV文件 using (StreamWriter writer = new StreamWriter(targetCsvPath)) { // 写入表头(根据你的实际列名调整) writer.WriteLine("Test File Name,Date Time,DeviceID,ResultValue"); foreach (var record in uniqueRecords) { // 处理带逗号的字段,用引号包裹避免格式错误 string line = $"\"{record.TestFileName}\",{record.DateTime.ToString("yyyy-MM-dd HH:mm:ss")},\"{record.DeviceID}\",{record.ResultValue}"; writer.WriteLine(line); } } Dts.TaskResult = (int)ScriptResults.Success; } catch (Exception ex) { // 将错误信息输出到SSIS日志 Dts.Events.FireError(0, "CSV处理失败", ex.Message + "\n" + ex.StackTrace, string.Empty, 0); Dts.TaskResult = (int)ScriptResults.Failure; } } } }
关键注意事项
- 实体类适配:根据CSV的实际列名和数据类型,修改
CsvRecord类的属性,确保一一对应。 - 日期格式匹配:
TryParseExact中的日期格式必须和CSV里Date Time字段的格式完全一致,否则会解析失败。 - SSIS变量传递:如果不想硬编码路径,可在脚本任务的「ReadOnlyVariables」中添加路径变量,通过
Dts.Variables["User::SourceCsvPath"].Value.ToString()获取值。 - 分组排序顺序:必须先排序再分组,否则分组后的记录顺序无法保证,这也是你之前GroupBy方法失败的核心原因——SQL里的
PARTITION BY是基于排序后的结果,C#中也要遵循「先排序、再分组取首条」的逻辑。
内容的提问来源于stack exchange,提问作者Amanda
相关产品推荐
相关产品推荐

