PAYATHON 2026

读取 SQL 文件并写入 CSV 时数据列错位

数据小陈

结论

数据列错位通常与 SQL 查询或 OleDbDataAdapter 读取无关,问题往往出在 CSV 字段没有按规则转义。

字段中只要有逗号,直接拼接到一行里就会被当成多个字段。双引号、回车和换行也可能破坏 CSV 结构。处理时需要注意:

  • 所有字段都用双引号包围;
  • 字段内部的 " 写成 "";
  • 表头和数据使用相同的转义方法;
  • 表头的数量和顺序必须与 SQL 查询结果一致。

为什么会发生错位

假设数据库中的地址是:

Flat 2, 18 High Street

如果直接输出:

1001,John,Flat 2, 18 High Street,London

CSV 解析器会把它识别成 5 个字段,而预期只有 4 个。从这里开始,后面的数据就会向右错一列。

正确格式是:

"1001","John","Flat 2, 18 High Street","London"

再看下面这个值:

John "Jack" Smith

应该写成:

"John ""Jack"" Smith"

用双引号包围字段后,字段中的换行符也能得到正确处理。

使用统一的 CSV 转义方法

可以先为所有字段编写一个转义函数:

using System;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

private static string EscapeCsvField(object value)
{
    if (value == null || value == DBNull.Value)
        return "\"\"";

    string text;

    if (value is DateTime dateTime)
    {
        text = dateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
    }
    else if (value is IFormattable formattable)
    {
        text = formattable.ToString(null, CultureInfo.InvariantCulture);
    }
    else
    {
        text = value.ToString();
    }

    return "\"" + text.Replace("\"", "\"\"") + "\"";
}

然后根据 DataTable 的列生成表头和数据:

DataTable employeeTable = accessDataSet.Tables["employee"];

using (StreamWriter writer = new StreamWriter(
    csvFileName,
    false,
    new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)))
{
    string headerLine = string.Join(
        ",",
        employeeTable.Columns
            .Cast<DataColumn>()
            .Select(column => EscapeCsvField(column.ColumnName)));

    writer.WriteLine(headerLine);

    foreach (DataRow row in employeeTable.Rows)
    {
        string csvLine = string.Join(
            ",",
            row.ItemArray.Select(EscapeCsvField));

        writer.WriteLine(csvLine);
    }
}

这样处理后,数据中的逗号、双引号、回车和换行都不会改变字段数量。

如果 CSV 必须使用自定义表头,建议把表头放进数组,不要手写一整段以逗号分隔的字符串:

string[] headers =
{
    "Reference",
    "Gender",
    "Title",
    "Forename",
    "Surname",
    "Address1",
    "Address2",
    "Address3",
    "Address4",
    "Postcode",
    "Country",
    "P46 Statement A,B or C",
    "P46(Expat) Statement A, B or C"
};

if (headers.Length != employeeTable.Columns.Count)
{
    throw new InvalidOperationException(
        $"表头有 {headers.Length} 列,但查询结果有 {employeeTable.Columns.Count} 列。");
}

writer.WriteLine(string.Join(",", headers.Select(EscapeCsvField)));

自定义表头也可能包含逗号,例如:

P46 Statement A,B or C

如果没有双引号,这段文字会被拆成两个表头字段。因此,表头同样需要经过 EscapeCsvField 处理。

改进数据库读取代码

连接、命令和适配器使用后都应及时释放。填充数据前还要清理同名数据表,避免多次点击按钮后残留旧数据:

string sql = File.ReadAllText(
    @"X:\ZoddsandEnds\Emer\SQL\Employees.txt");

string connectionString =
    "Provider=Microsoft.ACE.OLEDB.12.0;" +
    "Data Source=" + dataAddress + ";" +
    "Persist Security Info=False;";

try
{
    if (accessDataSet.Tables.Contains("employee"))
        accessDataSet.Tables.Remove("employee");

    using (OleDbConnection connection =
           new OleDbConnection(connectionString))
    using (OleDbCommand command =
           new OleDbCommand(sql, connection))
    using (OleDbDataAdapter adapter =
           new OleDbDataAdapter(command))
    {
        connection.Open();
        adapter.Fill(accessDataSet, "employee");
    }
}
catch (Exception ex)
{
    MessageBox.Show(
        "An error has occurred, failed to retrieve data from the database.\r\n" +
        ex.Message);
    return;
}

SQL 文件只是查询文本的来源,不负责 CSV 字段转义。如果查询结果已经正确写入 DataTable,就应该重点检查输出过程。

还需要检查的几个问题

表头字符串存在拼接错误

原代码中,下面两段之间缺少逗号:

"P46(Pen) Is Recently Bereaved, P46(Expat) Statement A, B or C" +
"P46 (Expat Student Loan Indicator, ..."

拼接后会变成:

P46(Expat) Statement A, B or CP46 (Expat Student Loan Indicator

结果是两个表头粘在一起。即使补上逗号,表头原本包含的逗号仍然需要转义,所以使用字符串数组生成表头更合适。

表头必须与查询列严格对应

还要逐项检查:

  • 表头数量是否等于 DataTable.Columns.Count;
  • 表头顺序是否与 SQL SELECT 的列顺序一致;
  • SQL 是否使用了 SELECT *;
  • 查询中是否有重名列或计算列。

建议在 SQL 中明确列出字段:

SELECT
    EmployeeReference,
    Gender,
    Title,
    Forename,
    Surname,
    Address1,
    Address2
FROM Employees;

不要依赖 SELECT * 的隐含列顺序。

不要通过删除逗号修复

删除地址、公司名称或备注中的逗号,可能暂时让列不再错位,却会损坏原始数据。正确的方法是转义字段,而不是修改字段内容。

Excel 的显示不一定代表文件结构错误

在某些地区设置下,Excel 默认使用分号而不是逗号作为列表分隔符。如果文件在文本编辑器或标准 CSV 解析器中结构正确,但用 Excel 打开后仍然显示异常,需要检查系统区域设置,或者使用 Excel 的”从文本/CSV”功能导入。

代码层面更稳妥的做法是使用成熟的 CSV 库,例如 CsvHelper,由它统一处理引号、分隔符、换行、编码和类型格式。如果不想引入第三方依赖,上面的 EscapeCsvField 配合 string.Join,也足以解决常见的列错位问题。

备注:内容仅供参考。