从整数和小数字符串中删除字符的最有效方法

时间:2019-06-21 18:21:05

标签: c# sql-server .net-core etl sql-server-2017

我正在将美国人口普查原始数据处理到SQL Server数据库中。解压缩后的tar文件会产生略多于14,000个CSV文件,需要将其处理到266个不同的数据库表中。我必须遍历每个CSV文件并向该文件附加一个标头,以便SSIS可以将原始数据ETL到目标SQL Server表中。

每个CSV文件的前6列完全相同。每个文件的其余列均不同。其余列中的数据大部分是数字值(整数和十进制)。但是,人口普查局会添加称为“ jam”值的字符,以表示为什么没有值。我需要将这些Jam值替换为null或空字符串,因为目标数据库表列为DECIMALS,并且jam值导致SSIS插入失败。

因此,我有一个C#(DotNet Core)类库,循环遍历14K文件。对于每个文件,我必须执行以下操作:

  1. 创建一个StringBuilder变量
  2. 将行标题附加到StringBuilder以便SSIS工作
  3. 循环遍历文件中的每一行
  4. 对于每一行,我必须拆分出前6列,因为我需要目标表中的那些字符串。然后,我将剩余的列分开,因为我必须删除留下数值数据的果酱值
  5. 将前6列合并,然后将已清理的数据排成一行
  6. 将新清除的行添加到StringBuilder
  7. 完成所有行的循环之后,将StringBuilder写入目标文件夹,SSIS将在该目标文件夹中加载到数据库中。

我有3个嵌套循环:

  1. 循环14000个文件
  2. 对于每个文件,遍历每一行
  3. 对于每一行,在列上循环以删除字符

这是我遍历每个文件的代码:

    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);
    }

这都是通过蛮力编程来完成的。有更有效的方法吗?

谢谢您的时间。

2 个答案:

答案 0 :(得分:0)

我有两个建议可以加快速度。 首先,由于对解析后的十进制值不做任何事情,因此可以使用正则表达式检查字符串是否仅包含数字。它比使用TryParse更快。我使用秒表来检查速度,这样在“假”情况下产生的性能会稍微好一些,而在“真”情况下产生的性能会好得多。因此,IsDecimal方法将变为:

private static bool IsDecimal(string text)
{
    var regex = @"^-?(0|[1-9]\d*)(\.\d+)?$";
    return Regex.Match(text, regex).Success;
}

第二个建议是将if-else块转换为if块。因此,这一行:

stringToClean[i] = (stringToClean[i].IsDecimal()) ? stringToClean[i] : "";

将成为这个:

if (!stringToClean[i].IsDecimal())
{
    stringToClean[i] = "";
}

答案 1 :(得分:0)

我建议重温流程设计。在适当的平衡中使用sql和ssis的功能。 使用siss循环浏览文件夹中的所有文件,并将原始文本行加载到新创建的原始表中。 然后使用sql代码进行其余处理。您可以使用charindex或patIndex函数拆分原始行,而SQL的一个好处是可以大大减少运行时间,因为您将在给定文件的单个事务中处理整个批处理。

另一个可能的好处是,您可能只需要为所有不同的文件创建一个原始表,该表具有三列-id,fileName和rawText。因此设计看起来像这样:

在SSIS中执行的步骤

  • 创建一个StringBuilder变量。将行标题附加到StringBuilder,以便SSIS遍历文件中的每一行。

使用SQL执行的步骤

  • 拆分前6列以获得 目标表中的字符串并拆分剩余的列以将其删除 卡纸值使用patindexcharindex函数与replace函数结合使用的单个选择语句使用数字语句保留数值数据,以使卡纸值无效。