【问题标题】:Importing CSV files to SQL Server database将 CSV 文件导入 SQL Server 数据库
【发布时间】:2016-06-20 07:12:02
【问题描述】:

.csv 文件有 57 列,其中一些值为空。例如,,Jane,Doe,,35。假设第一个值(在逗号之前)是自动 ID 键,每次导入数据库时​​都会递增。问题是未导入空值,从而导致错误。我在这里理解的是 SQL 数据库无法读取空值,因此它会移动这些值,如下所示:Jane,Doe,35,从而使Jane 成为ID 的值。另一个问题是.csv 文件的列与表不匹配。例如,表有一个ID 列,而.csv 文件以Name 列开头。有没有办法从特定列开始将其导入数据库?

注意:这仅在一张表中。 问题:如果我创建一个与.csv 文件的列匹配的单独表,然后将其与具有ID 列的表连接起来(在示例中),是否更可取?

到目前为止我的代码:

        DataTable dt = new DataTable();

        dt.Columns.AddRange(new DataColumn[54] { new DataColumn("Delay_Code"), and so on... });

        string csvData = File.ReadAllText(e.FullPath);
        foreach (string row in csvData.Split('\n'))
        {
            if (!string.IsNullOrEmpty(row))
            {
                dt.Rows.Add();
                int i = 0;
                foreach (string cell in row.Split(','))
                {
                    dt.Rows[dt.Rows.Count - 1][i] = cell;
                    i++;
                }
            }
        }

        string consString = @"Data Source="blahblah";
        using (SqlConnection con = new SqlConnection(consString))
        {
            using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(con))
            {
                //Set the database table name
                sqlBulkCopy.DestinationTableName = "owner.Table";
                con.Open();
                sqlBulkCopy.WriteToServer(dt);
                con.Close();
            }
        }

对于上面的代码,我创建了另一个与.csv 文件的列匹配的表。最初,该表在.csv 文件中的列之前有三列。

最好的方法是什么?

【问题讨论】:

    标签: c# sql-server csv


    【解决方案1】:

    从某些角度来看,您的问题可能看起来过于宽泛,但我理解它,并且我正在分享一些对我有用且您可以用作灵感的宽泛(通用)方法:

    1. 写一个优质的CSV阅读器,也可以像处理CSV行

      Value1, Value2, "Value 3", "Value ""4""", "Value
      5",, Value 7, "Value,8"
      
      • 因此,如果有双引号字符,它可以接受任何字符,包括空格或换行符、其他引号(写为""),并且仅在另一个单引号(")处停止
      • 准备好分隔符可以是,; 或制表符,十进制数字可以包含逗号,如., 等。请记住,生成CSV 通常是另一方,您可能需要适应其格式
      • 最简单的方法是为此使用有限状态机,它轻巧且快速,例如 50 MB/秒。
    2. [可选] 逻辑步骤是使用可配置的导入定义而不是硬连线的定义,例如

      { ExternalOrders = "Data From External Orders in CSV",
        CsvFormat = { CsvHasHeaderRow = true,
                   CsvFieldSeparator = ",",
                   DecimalSeparator = ".",
                   DateFormat = "yyyy-MM-dd",
                   DateTimeFormat = "yyyy-MM-dd hh:mm:ss",
                   TimeFormat = "hh:mm:ss" },
        ColumnMap = {
             Column1 = { SourceColumnName = "OrderID",
                         SourceColumnType = "nvarchar(50)"
                         StagingColumn = 1 },
             Column2 = { SourceColumnName = "OrderDate",
                         SourceColumnType = "date"
                         StagingColumn = 2 },
             ColumnAmount = { SourceColumnPosition = 5,
                         SourceColumnType = "decimal(18,6)"
                         StagingColumn = 3 }
             },
      StagingImportSql = "INSERT INTO Orders (Number, OrdDate, Amount)
                              SELECT CAST(c1 AS navarchar(50),
                                      CAST(c2 AS date),
                                      CAST(c3 AS money)
                                  FROM StagingTable
                                  WHERE ImportID = {{ImportIDToken}};"
      }
      
      • 这可以在数据库中,在文本配置文件中,在任何地方。我有 3 个数据库表(ImportAction、CsvFormat、ColumnMapping)
    3. 在您的 SQL 数据库中有一个带有列的临时表

      ID int identity(1,1)
      ImportID int
      CsvRowNumber int
      c1 sqlvariant
      c2 sqlvariant
      c3 sqlvariant
      ...
      c64 sqlvariant
      
    4. 根据配置的ColumnMap创建调用CSV导入(格式来自CsvFormat)的引擎来填充暂存表。在您的临时表中还有其他列,例如ImportIDCsvRecordNumber 在导入期间由引擎自动填充。为每个 CSV 发出 SQL INSERT 语句,以便将一条记录添加到临时表中。全部完成后,将其 SQL 命令中的令牌 {{ImportIDToken}} 替换为实际导入的编号后,启动 StagingImportSql

    5. 您可以选择实施

      • 如果您在导入后不立即清除暂存表,则清除它,例如

        • 保留一定数量的进口和/或
        • 保持导入不超过给定天数
      • 用户限制 - 谁可以查看和运行哪些导入

    我没有分享具体的代码,在我的例子中它在 中,它在许多类中可能有 6000 行。我只是分享想法。


    解析注意事项:

    • 让您的 CSV 阅读器返回字符串的 DataTable
      • 返回整个表不适用于大型 CSV,但最多(假设)500 MB 应该没问题
    • 使用Integer.Parse()Decimal.Parse()Double.Parse() 等方法根据列数据类型解析简单值。您可能希望在解析之前将自定义小数分隔符或千位分隔符替换为标准分隔符。
    • 对于日期和时间,使用DateTime.ParseExact()
    • 对于数据类型的内部处理,枚举System.Data.SqlDbType是你的朋友

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-09-20
      • 1970-01-01
      • 1970-01-01
      • 2016-05-14
      • 2013-02-20
      • 2010-10-04
      • 1970-01-01
      相关资源
      最近更新 更多