You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从Oracle向PostgreSQL插入数据时出现22P02错误求助

Oracle转PostgreSQL迁移API报错:22P02 invalid input syntax for type numeric

我正在开发一个API,用于将数据从Oracle迁移至PostgreSQL。流程是从Oracle查询数据存入DataTable,再生成Insert SQL到PostgreSQL执行。但表中包含numeric或timestamp类型字段时,会抛出**"22P02: invalid input syntax for type numeric"**错误。以下是我的代码:

public void Oracle2PG(string fromTable, string toTable)
{
    string pgConn = _configuration.GetValue<string>($"ConnectionStrings:PGConnection");
    string oraConn = _configuration.GetValue<string>($"ConnectionStrings:OracleConnection");

    using (var pgConn = new NpgsqlConnection(sctPgConn))
    using (var oraConn = new OracleConnection(sctOraConn))
    {
        string oraSql = $"select * from {fromTable}";
        var dtOra = GetDatatable(oraSql, "", oraConn);
        string insertSql = GenerateInsertSql(toTable, dtOra);
        pgConn.Execute(insertSql); //occur error "22P02: invalid input syntax for type numeric"
    }
}

private static DataTable GetDatatable(string sql, object para, OracleConnection oraConn)
{
    var res = oraConn.Query(sql, para);
    string json = JsonConvert.SerializeObject(res);
    var dtResult = (DataTable)JsonConvert.DeserializeObject(json, (typeof(DataTable)));

    return dtResult;
}

private static string GenerateInsertSql(string tableName, DataTable dataTable)
{
    var cols = (from DataColumn column in dataTable.Columns select column.ColumnName).ToList();
    string colNames = string.Join(", ", cols);
    string sql = $"Insert into {tableName} ({colNames}) values";
    int cnt = 0;
    foreach (DataRow row in dataTable.Rows)
    {
        cnt += 1;
        sql += $"(";
        var rowValues = row.ItemArray.Select(s => s.ToString()).ToList();
        foreach (string v in rowValues)
        {
            sql += $"'{v}',";
        }
        sql = sql.Substring(0, sql.Length - 1);
        if (cnt == dataTable.Rows.Count)
        {
            sql += ") ";
        }
        else
        {
            sql += "), ";
        }
    }
    return sql;
}

问题根源

  • 类型处理错误:生成Insert SQL时,把所有字段值都用单引号包裹,但numeric类型不需要单引号,timestamp类型的格式也可能和PostgreSQL要求不匹配,直接导致语法错误。
  • 数据类型丢失:通过Json序列化再反序列化生成DataTable的过程中,原本的数值、日期类型会被转成字符串,丢失了原始类型信息,无法准确判断字段类型。
  • SQL注入风险:直接拼接表名、字段值到SQL语句中,存在严重的SQL注入漏洞。
  • 批量插入效率低:拼接单条大SQL插入大量数据,不仅容易出错,还会拖慢数据库性能。

解决方案

1. 用参数化查询替代SQL拼接(最基础的修复)

参数化查询能自动处理不同数据类型的格式,避免语法错误,同时杜绝SQL注入。修改后的核心代码如下:

public void Oracle2PG(string fromTable, string toTable)
{
    string pgConnStr = _configuration.GetValue<string>($"ConnectionStrings:PGConnection");
    string oraConnStr = _configuration.GetValue<string>($"ConnectionStrings:OracleConnection");

    using (var pgConn = new NpgsqlConnection(pgConnStr))
    using (var oraConn = new OracleConnection(oraConnStr))
    {
        pgConn.Open();
        oraConn.Open();

        string oraSql = $"select * from {fromTable}";
        // 直接获取Oracle数据的原始类型,跳过Json序列化步骤
        var oraData = oraConn.Query<dynamic>(oraSql).ToList();
        if (!oraData.Any()) return;

        // 提取字段名
        var firstRow = oraData.First();
        var cols = ((IDictionary<string, object>)firstRow).Keys.ToList();
        string colNames = string.Join(", ", cols);
        // 生成参数占位符
        string paramPlaceholders = string.Join(", ", cols.Select((_, idx) => $"@p{idx}"));
        string insertSql = $"INSERT INTO {toTable} ({colNames}) VALUES ({paramPlaceholders})";

        using (var cmd = new NpgsqlCommand(insertSql, pgConn))
        {
            foreach (var row in oraData)
            {
                cmd.Parameters.Clear();
                var rowDict = (IDictionary<string, object>)row;
                for (int i = 0; i < cols.Count; i++)
                {
                    var value = rowDict[cols[i]];
                    // 处理Oracle的空值
                    cmd.Parameters.AddWithValue($"@p{i}", value == DBNull.Value ? null : value);
                }
                cmd.ExecuteNonQuery();
            }
        }
    }
}

2. 优化DataTable的获取方式(保留原始类型)

如果必须使用DataTable,不要用Json序列化,直接用DataAdapter填充,保留原始数据类型:

private static DataTable GetDatatable(string sql, OracleConnection oraConn)
{
    var dt = new DataTable();
    using (var cmd = new OracleCommand(sql, oraConn))
    using (var adapter = new OracleDataAdapter(cmd))
    {
        adapter.Fill(dt);
    }
    return dt;
}

然后生成参数化SQL时,根据DataColumn的类型处理值:

private static void BulkInsert(DataTable dt, string toTable, NpgsqlConnection pgConn)
{
    string colNames = string.Join(", ", dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName));
    string paramPlaceholders = string.Join(", ", dt.Columns.Cast<DataColumn>().Select((_, idx) => $"@p{idx}"));
    string insertSql = $"INSERT INTO {toTable} ({colNames}) VALUES ({paramPlaceholders})";

    using (var cmd = new NpgsqlCommand(insertSql, pgConn))
    {
        foreach (DataRow row in dt.Rows)
        {
            cmd.Parameters.Clear();
            for (int i = 0; i < dt.Columns.Count; i++)
            {
                var colType = dt.Columns[i].DataType;
                var value = row[i];
                cmd.Parameters.AddWithValue($"@p{i}", value == DBNull.Value ? DBNull.Value : Convert.ChangeType(value, colType));
            }
            cmd.ExecuteNonQuery();
        }
    }
}

3. 使用Npgsql批量复制(大数据量最优解)

如果迁移的数据量很大,推荐用Npgsql的NpgsqlBinaryImporter,效率远高于普通插入,还能精准处理类型映射:

public void Oracle2PGBulk(string fromTable, string toTable)
{
    string pgConnStr = _configuration.GetValue<string>($"ConnectionStrings:PGConnection");
    string oraConnStr = _configuration.GetValue<string>($"ConnectionStrings:OracleConnection");

    using (var pgConn = new NpgsqlConnection(pgConnStr))
    using (var oraConn = new OracleConnection(oraConnStr))
    {
        pgConn.Open();
        oraConn.Open();

        string oraSql = $"select * from {fromTable}";
        var dt = GetDatatable(oraSql, oraConn);
        if (dt.Rows.Count == 0) return;

        // 开启二进制批量导入
        using (var writer = pgConn.BeginBinaryImport($"COPY {toTable} FROM STDIN BINARY"))
        {
            foreach (DataRow row in dt.Rows)
            {
                writer.StartRow();
                foreach (DataColumn col in dt.Columns)
                {
                    var value = row[col];
                    if (value == DBNull.Value)
                    {
                        writer.WriteNull();
                        continue;
                    }
                    // 根据Oracle类型映射到PostgreSQL对应类型
                    switch (col.DataType.Name)
                    {
                        case "Decimal":
                            writer.Write((decimal)value, NpgsqlDbType.Numeric);
                            break;
                        case "DateTime":
                            writer.Write((DateTime)value, NpgsqlDbType.TimestampTz);
                            break;
                        case "Int32":
                            writer.Write((int)value, NpgsqlDbType.Integer);
                            break;
                        // 其他类型按需添加,比如字符串、布尔值等
                        default:
                            writer.Write(value.ToString());
                            break;
                    }
                }
            }
            writer.Complete();
        }
    }
}

内容的提问来源于stack exchange,提问作者EvaHHHH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 16:15:35