【问题标题】:most efficient way to remove characters from a string of integers and decimals从整数和小数字符串中删除字符的最有效方法
【发布时间】: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)类库。对于每个文件,我必须执行以下操作:

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

这一切都是通过蛮力编程来实现的。有没有更有效的方法来做到这一点?

感谢您的宝贵时间。

【问题讨论】:

  • 我会分析它并找出它花费最多时间的地方。如果它花费大量时间分配内存和复制,您可能会在缓冲区中完成所有或大部分内容。编写和维护这将是一个挑剔的 PITA,所以我必须确保在编写它之前有一个值得的回报。但如果它是 IO 绑定的,那么这种努力将比浪费更糟糕。
  • 我不会将所有文件读入内存然后处理。考虑这一点,并可能采用逐行异步读取方法stackoverflow.com/q/27681849/3225
  • 我最初的开发工作不包括我正在清除果酱值的部分。我只是将标题添加到字符串生成器,然后将每一行附加到字符串生成器。完成所有 14000 个文件可能需要 5 分钟。也许再长一点。使用清洁卡纸值,14000 个文件大约需要 30 分钟。这是宜居的,因为这种情况并不经常发生。我只是好奇如何让我拥有的东西更有效率。谢谢。
  • 我不知道它是否更有效,但该方法可以减少到几行(加上 try/catch):try { decimal temp; File.WriteAllLines(censusDataDestinationFilePath, File.ReadLines(censusDataFilePath).Select(row =&gt; string.Join(",", row.Split(',').Select(item =&gt; decimal.TryParse(item, out temp) ? item : "")))); return true; } catch { return false; }
  • “这都是通过蛮力编程实现的。有没有更有效的方法来做到这一点?” 似乎更像是codereview.stackexchange.com 的问题

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


【解决方案1】:

我有两个建议可以加快速度。 首先,由于您不对生成的解析十进制值执行任何操作,因此您可以使用正则表达式来检查字符串是否仅包含数字。它比使用 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] = "";
}

【讨论】:

    【解决方案2】:

    我建议重新审视流程设计。在适当的平衡中使用 sql 和 ssis 的强大功能。 使用 ssis 遍历文件夹中的所有文件并将原始文本行加载到新创建的原始表中。 然后使用sql代码完成剩下的处理。您可以使用 charindex 或 patIndex 函数来拆分原始行,SQL 的一个好处是运行时间大大减少,因为您将在单个事务中处理给定文件的整个批处理。

    另一个可能的好处是您可能只需要为所有不同的文件创建一个原始表,其中包含三列 - id、fileName、rawText。所以设计看起来像:

    在 SSIS 中执行的步骤

    • 创建一个 StringBuilder 变量。将行标题附加到 StringBuilder 以便 SSIS 在文件中的每一行上循环工作。

    在 SQL 中执行的步骤

    • 拆分前 6 列得到 目标表中的字符串并拆分剩余的列以删除 使用patindexcharindex 函数结合replace 函数来消除堵塞值,使用单个选择语句留下数字数据。

    【讨论】:

      猜你喜欢
      • 2015-11-11
      • 2010-11-10
      • 2015-08-16
      • 2011-01-11
      • 1970-01-01
      • 1970-01-01
      • 2018-06-06
      • 2022-11-16
      • 2017-05-19
      相关资源
      最近更新 更多