【发布时间】:2019-06-21 18:21:05
【问题描述】:
我正在将原始美国人口普查数据处理到 SQL Server 数据库中。解压后的 tar 文件会产生超过 14,000 个 CSV 文件,需要处理成 266 个不同的数据库表。我必须遍历每个 CSV 文件并将标题附加到文件中,以便 SSIS 可以将原始数据 ETL 到目标 SQL Server 表中。
每个 CSV 文件的前 6 列完全相同。每个文件的其余列是不同的。其余列中的数据大多是数值(整数和小数)。但是,人口普查局添加了称为“果酱”值的字符,表示为什么没有值。我需要用 null 或空字符串替换这些 jam 值,因为目标数据库表列是 DECIMALS 并且 jam 值会导致 SSIS 插入失败。
所以,我有一个循环超过 14K 文件的 C#(DotNet Core)类库。对于每个文件,我必须执行以下操作:
- 创建一个 StringBuilder 变量
- 将行标题附加到 StringBuilder 以便 SSIS 工作
- 循环遍历文件中的每一行
- 对于每一行,我必须拆分前 6 列,因为我需要目标表中的那些字符串。然后我拆分剩余的列,因为我必须删除留下数字数据的堵塞值
- 将前 6 列和清理后的数据组合回一行
- 将新清理的行附加到 StringBuilder
- 完成所有行的循环后,将 StringBuilder 写入目标文件夹,SSIS 将在该文件夹中加载到数据库中。
我有 3 个嵌套循环:
- 循环超过 14000 个文件
- 对于每个文件,循环遍历每一行
- 对于每一行,循环删除字符的列
这是我循环遍历每个文件的代码:
private static Boolean BuildCensusDataFileWithHeader(String censusDataFilePath, String rowHeader, String censusDataDestinationFilePath)
{
try
{
// BUILD NEW FILE WITH HEADER
StringBuilder currentContent = new StringBuilder();
currentContent.Append(rowHeader + Environment.NewLine);
//RETRIEVE ALL LINES IN TARGET FILE
List<String> rawList = File.ReadAllLines(censusDataFilePath).ToList();
// LOOP THROUGH EACH LINE AND REMOVE ANY STRINGS IN COLUMNS AFTER COLUMN 6
// NOTE: COLUMNS 1-6 CONTAINS STRINGS NEEDED IN DATABASE
foreach (var row in rawList)
{
//TURN COMMA DELIMITED ROW OF DATA INTO ARRAY
String[] rowArray = row.Split(",");
// PEEL OFF FIRST 6 COLUMNS TO BE KEPT AS IS
IList<String> goodStrings = rowArray.Take(6).ToList();
// RETRIEVE REMAINING COLUMNS TO BE CLEANED OF STRINGS
IList<String> stringsToNullList = rowArray.Skip(6).ToList();
// REMOVE ALL STRINGS
stringsToNullList.OnlyDecimalValues();
// PUT GOOD COLUMNS AND CLEANED COLUMNS BACK TOGETHER AS A ROW
var cleanedRow = $"{String.Join(",", goodStrings)},{String.Join(",", stringsToNullList)}";
// APPEND ROW TO NEW DOCUMENT TO BE WRITTEN TO TARGET DIRECTORRY CONTAINING CLEANED DATA
currentContent.Append(cleanedRow + Environment.NewLine);
}
File.WriteAllText(censusDataDestinationFilePath, currentContent.ToString());
return true;
}
catch (Exception ee)
{
string temp = ee.Message;
return false;
}
}
这是我用空格替换字符的扩展方法:
public static void OnlyDecimalValues(this IList<String> stringToClean)
{
for (int i = 0; i < stringToClean.Count; ++i)
{
stringToClean[i] = (stringToClean[i].IsDecimal()) ? stringToClean[i] : "";
}
}
public static bool IsDecimal(this string text)
{
decimal test;
return decimal.TryParse(text, out test);
}
这一切都是通过蛮力编程来实现的。有没有更有效的方法来做到这一点?
感谢您的宝贵时间。
【问题讨论】:
-
我会分析它并找出它花费最多时间的地方。如果它花费大量时间分配内存和复制,您可能会在缓冲区中完成所有或大部分内容。编写和维护这将是一个挑剔的 PITA,所以我必须确保在编写它之前有一个值得的回报。但如果它是 IO 绑定的,那么这种努力将比浪费更糟糕。
-
我不会将所有文件读入内存然后处理。考虑这一点,并可能采用逐行异步读取方法stackoverflow.com/q/27681849/3225
-
我最初的开发工作不包括我正在清除果酱值的部分。我只是将标题添加到字符串生成器,然后将每一行附加到字符串生成器。完成所有 14000 个文件可能需要 5 分钟。也许再长一点。使用清洁卡纸值,14000 个文件大约需要 30 分钟。这是宜居的,因为这种情况并不经常发生。我只是好奇如何让我拥有的东西更有效率。谢谢。
-
我不知道它是否更有效,但该方法可以减少到几行(加上 try/catch):
try { decimal temp; File.WriteAllLines(censusDataDestinationFilePath, File.ReadLines(censusDataFilePath).Select(row => string.Join(",", row.Split(',').Select(item => decimal.TryParse(item, out temp) ? item : "")))); return true; } catch { return false; } -
“这都是通过蛮力编程实现的。有没有更有效的方法来做到这一点?” 似乎更像是codereview.stackexchange.com 的问题
标签: c# sql-server .net-core etl sql-server-2017