【问题标题】:Bulk Insert with optional text qualifier带有可选文本限定符的批量插入
【发布时间】:2010-11-25 14:21:30
【问题描述】:

我正在使用批量插入将 csv 导入数据库。 它是逗号分隔的 csv 文件。所有字段都没有文本限定符。

但某些字段可能包含逗号作为数据的一部分。 例如,地址字段值。这些值用双引号括起来。 只有当字段值中包含逗号时才会出现这些双引号,否则值不会被双引号括起来。 因此,在某些行中,ADDRESS 值用双引号括起来,但在其他行中则不是。 有没有办法在批量插入命令中指定文本限定符?

我尝试使用格式文件选项进行批量插入。

BULK INSERT Test_Imported FROM 'C:\test.csv' 
WITH (FIRSTROW=0,FIELDTERMINATOR = ',',ROWTERMINATOR = '\n',FORMATFILE = 'C:\test.Fmt')

但我无法将双引号作为格式文件中的可选文本限定符。

PS: 这个函数实际上是更大模块的一部分,它是用 c# 编写的。 批量插入命令是从 c# 调用的。

csv 文件是通过电子邮件从另一个自动化系统传来的。我无法控制格式 csv 文件。大约有 150 列。 每个 csv 文件中平均有 12000 行。 忘记指定数据库。它是 SQL Server 2005。

【问题讨论】:

标签: c# sql sql-server csv bulkinsert


【解决方案1】:

对我有用的是改变

ROWTERMINATOR = '\n'

收件人:

ROWTERMINATOR = '0x0a'

【讨论】:

    【解决方案2】:

    我知道这是一个老问题,但我有一个 TSQL 方法来处理间歇性引号分隔符。它可能不漂亮,但它可能对在这里找到方法的人有所帮助:

    1. 导入文本文件,每行在一列 - 一个字段。
    2. 使用下面的更新语句将引号之间的逗号更改为一些可识别的字符串,在本例中为*&*
    3. 使用另一个更新语句去除所有引号。
    4. 使用 bcp 将数据导出到新的 CSV 文件中。
    5. 使用新 CSV 文件中的所有字段将您的批量导入到原始表中:现在没有引号,并且字段内逗号改为 &,所以一个简单的逗号分隔导入将起作用。
    6. 使用另一个更新语句将 & 改回逗号。

    UPDATE InitialTable SET BulkColumn = REPLACE(BulkColumn, SubString(BulkColumn, CHARINDEX('"', BulkColumn, 0), CHARINDEX('"', BulkColumn, CHARINDEX('"', BulkColumn, 0) + 1) - CHARINDEX('"', BulkColumn, 0) + 1), REPLACE( SubString(BulkColumn, CHARINDEX('"', BulkColumn, 0), CHARINDEX('"', BulkColumn, CHARINDEX('"', BulkColumn, 0) + 1) - CHARINDEX('"', BulkColumn, 0) + 1), ',', '*&*')) WHERE BulkColumn LIKE '%"%'

    【讨论】:

      【解决方案3】:

      Bulk Insert 语句真的很烂,因为它不处理可选的限定符。

      TextFieldParser类可以帮助我们清理文件(Microsoft.VisualBasic.FileIO.TextFieldParser

      我粘贴了一个函数,该函数使用 TextFieldParser 类来清理分隔文件,以便您可以在批量插入语句中使用它。

      String newDel = CleanDelimitedFile("c:\temp.csv",new String[] {","},"\t,\t");
      

      这是一个清理分隔文件的函数。

          /// <summary>
          /// This function opens a delimited file and cleans up any string quantifiers
          /// </summary>
          /// <param name="FileFullPath">Full path of the delimited string</param>
          /// <param name="CurrentDelimiter">What string / character the file uses as the delimiter</param>
          /// <param name="NewDelimiter">What new delimiter string to use</param>
          /// <returns>Returns String representation of the new delimited file</returns>
          private static String CleanDelimitedFile(String FileFullPath, String[] CurrentDelimiter, String NewDelimiter) {
      
              //-- if the file exists stream it to host
              if (System.IO.File.Exists( FileFullPath )) {
                  Microsoft.VisualBasic.FileIO.TextFieldParser cvsParser = null;
                  System.Text.StringBuilder parseResults = new System.Text.StringBuilder();
                  try {
                      // new parser
                      cvsParser = new Microsoft.VisualBasic.FileIO.TextFieldParser(FileFullPath);
                      // delimited file has certain fields enclosed in quotes
                      cvsParser.HasFieldsEnclosedInQuotes = true;
                      // the current delimiter
                      cvsParser.Delimiters = CurrentDelimiter;
                      // iterate through all the lines of the file
                      Boolean FirstLine = true;
                      while (!cvsParser.EndOfData ) {
                          if (FirstLine) {
                              FirstLine = false;
                          }
                          else {
                            parseResults.Append("\n");  
                          }
                          Boolean FirstField = true;
                          // iterate through each field
                          foreach (String item in cvsParser.ReadFields()) {
                              if (FirstField) {
                                  parseResults.Append(item);
                                  FirstField = false;
                              } 
                              else {
                                  parseResults.Append(NewDelimiter + item);
                              }
                          }
      
                      }
                      return parseResults.ToString();
                  }
                  finally {
                      if (cvsParser != null) {
                          cvsParser.Close();
                          cvsParser.Dispose();
                      }
                  }
              }
              return String.Empty;
          }
      

      【讨论】:

        【解决方案4】:

        遗憾的是,SQL 2005 和 2008 导入 XLS 文件比 CSV 文件更顺畅。我从来没有反对过微软,但除非数据库管理的所有 ANSI 标准都发生了巨大的变化,并且文本限定符的概念正在被放弃(我非常怀疑),否则这可能是 MS 的专有举措。 SQL 2000 可以很好地处理文本限定符(不确定 BULK 命令,因为我一直只使用导入向导)。想象一下当我们迁移到 2005 年时我的惊讶,我不得不重新处理我的所有流程以不导入平面文件而是导入 XLS。我只花了 16 个小时(是的,两个工作日)就得出了这个结论,那一周我实际上失眠了,因为我对 MS 不允许使用文本限定符感到非常沮丧(我什至去我的老板办公室道歉花了这么多时间在本来应该是 10 分钟的任务上)。具有讽刺意味的是,您不能告诉 Excel 导出任何内容而不包括双引号文本限定符(或几乎任何其他软件导出器)。 GRRRRRR。

        所有这一切中最令人沮丧的部分是 SQL 2005 导入向导有一个定义文本限定符的地方!

        ...我敢说,在这次经历之后,我开始理解所有反 M$ 的言论了!

        【讨论】:

          【解决方案5】:

          很遗憾,您必须对文件进行预处理以使其保持一致。 SQL 批量操作在字段分隔符上拆分字符串。

          一些选项:

          • 在 C# 中处理将不被引号括起来的逗号更改为管道 (|)
          • 将文件分成 2 个:“和非”文件。这仅在同一字段具有“

          你说你无法控制格式,但你所拥有的却无法使用……

          【讨论】:

          • 感谢朋友的快速帮助。是否有任何其他独占字符可用作分隔符? (这个 csv 已经有 |,*,$,^ 作为数据的一部分)我正在考虑使用正则表达式来替换分隔符。 C# 中处理大约 8Mb csv 文件的最佳方式是什么?
          • 你可以选择任何字符,甚至是双打或序列。抱歉,我是 SQL 人,不是 C#。
          猜你喜欢
          • 2016-11-13
          • 1970-01-01
          • 2014-11-01
          • 2010-12-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-08
          相关资源
          最近更新 更多