【问题标题】:C# SqlBulkCopy resume on errorC# SqlBulkCopy 错误恢复
【发布时间】:2014-02-12 12:03:52
【问题描述】:

我正在尝试使用 BulkCopy.WriteToServer() 将 1000 万行从 Oracle 批量插入到 SQL Server。

我确定了

  • 两边的表列和数据类型是一样的。我的意思是 Oracle 的 Date 数据类型映射到 Sql Server 的 datetime 数据类型。 Varchar2 映射到 varchar 等。
  • 目标表上没有触发器和索引

当涉及到大约 140 万行时,它因 System.ArgumentOutOfRangeException 失败:Hour、Minute 和 Second 参数描述了无法表示的 DateTime。在 System.DateTime.DateToTicks(Int32 年,Int32 月,Int32 日)

这是我的代码

      SqlBulkCopy copy;
      copy = new SqlBulkCopy(destConn, SqlBulkCopyOptions.TableLock, null); 
      // ColumnMappings property is used to map column positions, not data type
      copy.DestinationTableName = DestTable;
      copy.NotifyAfter = 5000; 
      copy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied);
      copy.BulkCopyTimeout = 0;
      try { copy.WriteToServer((IDataReader)rd); }
      catch (Exception ex)
      {
        AppInfo.TableMsg[SrcTable] = AppInfo.TableMsg[SrcTable] + "\r\n" + "bulkcopy.WriteToServer(rd) failed. " + ex.Message;
        throw ex;
      }

我的表有 100 多列,有 26 个 DATE 列。坏数据在哪里很难分清

所以我这里有 3 个问题

  1. 是否有任何设置/选项可以使 WriteToServer() 继续或忽略异常?或者我可以在 catch 块中做些什么来让它继续?我不在乎留下不好的数据。我正在寻找一种方法来告诉它在插入错误时继续。
  2. 有什么方法可以防止这种情况发生吗?例如,我可以在填充 OracleDataReader 的选择查询中做什么?
  3. 如果以上2个问题都没有解决,那么有没有什么好的方法可以清除Oracle方面的“坏日期”?

谢谢,

更新: 我做了以下

  1. 将目标表数据类型从 datetime 更改为 datetime2
  2. 修改选择列表为

    CASE WHEN my_date_column

    对于所有具有 DATE 数据类型的列。

但错误仍然存​​在。这是完整的错误消息。

System.ArgumentOutOfRangeException was caught
  HResult=-2146233086
  Message=Hour, Minute, and Second parameters describe an un-representable DateTime.
  Source=mscorlib
  StackTrace:
       at System.DateTime.TimeToTicks(Int32 hour, Int32 minute, Int32 second)
       at Oracle.DataAccess.Client.OracleDataReader.GetDateTime(Int32 i)
       at Oracle.DataAccess.Client.OracleDataReader.GetValue(Int32 i)
       at System.Data.SqlClient.SqlBulkCopy.GetValueFromSourceRow(Int32 destRowIndex, Boolean& isSqlType, Boolean& isDataFeed, Boolean& isNull)
       at System.Data.SqlClient.SqlBulkCopy.ReadWriteColumnValueAsync(Int32 col)
       at System.Data.SqlClient.SqlBulkCopy.CopyColumnsAsync(Int32 col, TaskCompletionSource`1 source)
       at System.Data.SqlClient.SqlBulkCopy.CopyRowsAsync(Int32 rowsSoFar, Int32 totalRows, CancellationToken cts, TaskCompletionSource`1 source)
       at System.Data.SqlClient.SqlBulkCopy.CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, String updateBulkCommandText, CancellationToken cts, TaskCompletionSource`1 source)
       at System.Data.SqlClient.SqlBulkCopy.CopyBatchesAsync(BulkCopySimpleResultSet internalResults, String updateBulkCommandText, CancellationToken cts, TaskCompletionSource`1 source)
       at System.Data.SqlClient.SqlBulkCopy.WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet internalResults, CancellationToken cts, TaskCompletionSource`1 source)
       at System.Data.SqlClient.SqlBulkCopy.WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletionSource`1 source)
       at System.Data.SqlClient.SqlBulkCopy.WriteToServerInternalAsync(CancellationToken ctoken)
       at System.Data.SqlClient.SqlBulkCopy.WriteRowSourceToServerAsync(Int32 columnCount, CancellationToken ctoken)
       at System.Data.SqlClient.SqlBulkCopy.WriteToServer(IDataReader reader)

从错误消息看来,有问题的部分是 OracleDataReader 而不是 SqlBulkCopy。

如何使用 Oracle 查询快速发现这些违规值? 还有什么建议吗?

【问题讨论】:

    标签: c# sql-server oracle etl


    【解决方案1】:

    Oracle 数据库可以存储儒略时代的日期,范围从公元前 4712 年 1 月 1 日到公元 9999 年 12 月 31 日(共同时代,或“公元”)。除非专门使用 BCE(格式掩码中的“BC”),否则 CE 日期条目是默认值。

    SQL Server 的datetime 无法做到这一点。 datetime2 推荐用于新开发,它可以保存所有实用的日期和时间值。如果仍然达到任何范围限制,请运行 SELECT * FROM T WHERE SomeDateCol < '0000-01-01' 样式的 Oracle 查询来查找无效数据。

    TL;DR:研究确切的 supported value ranges 并找到任何无法映射的值。

    您的问题:

    1. 不,SQL Server 不能这样做,唉。
    2. 是的,以不同的方式处理无效行。也许将它们过滤掉或将无效值转换为NULL。您的选择。
    3. 见上文。

    【讨论】:

    • 我用更多信息更新了我的问题。需要进一步的帮助。
    • OracleDataReader.GetDateTime出现错误说明SQL Server与此无关。读取时出现错误。 .NET DateTime(与 datetime2 具有完全相同的范围)无法表示的某个日期值仍然存在。在 Oracle 查询工具中执行查询并检查结果。一定有什么错误。您可以通过以下方式将错误行一分为二:将查询更改为仅返回前 1M 行。如果错误再次发生,则在前 1M 行中。如果不是,他们很清楚。排除它们并再次平分。找到错误的行。
    • 我明白了。谢谢你的帮助。我很感激。
    【解决方案2】:

    好的。我想通了。我正在回答我的第二个和第三个问题。

    Oracle 中的错误日期类似于“01/26/2006 17:94:00”。

    To_char(my_column,'hh24:mi:ss') 显示 '00:00:00' To_char(my_column,'mi') 显示 '00'

    显示为有效数据,使用to_char()函数作为过滤器无法识别为无效

    我能做的是使用转储功能

    DELETE FROM my_table
    WHERE my_column IS NOT NULL
      AND (To_Number(SubStr(Dump(my_column), InStr(Dump(my_column),':',1,1)+2, InStr(Dump(my_column),',',1,1)-InStr(Dump(my_column),':',1,1)-2))-100 < 0
       OR To_Number(SubStr(Dump(my_column), InStr(Dump(my_column),',',1,2)+1, InStr(Dump(my_column),',',1,3)-1-InStr(Dump(my_column),',',1,2))) NOT BETWEEN 1 AND 12
       OR To_Number(SubStr(Dump(my_column), InStr(Dump(my_column),',',1,3)+1, InStr(Dump(my_column),',',1,4)-1-InStr(Dump(my_column),',',1,3))) NOT BETWEEN 1 AND 31
       OR To_Number(SubStr(Dump(my_column), InStr(Dump(my_column),',',1,4)+1, InStr(Dump(my_column),',',1,5)-1-InStr(Dump(my_column),',',1,4))) NOT BETWEEN 1 AND 24
       OR To_Number(SubStr(Dump(my_column), InStr(Dump(my_column),',',1,5)+1, InStr(Dump(my_column),',',1,6)-1-InStr(Dump(my_column),',',1,5))) NOT BETWEEN 1 AND 60
       OR To_Number(SubStr(Dump(my_column), InStr(Dump(my_column),',',-1)+1)) NOT BETWEEN 1 AND 60)
    

    这会清除不良数据。

    【讨论】:

      【解决方案3】:

      “usr”的回答很好地描述了这个问题。您可以对具有问题日期的源 Oracle 源列执行 CASE 语句,以将无效日期转换为 NULL 或默认值。现在,识别有问题的行或列是一个大问题。我想出了一种方法来识别问题。

      请阅读我的这篇博文以识别问题列,以便您可以进行适当的 DECODE 将问题日期转换为 NULL 或有效的默认值 https://sqljana.wordpress.com/tag/datetime-odp-net-oracle/

      【讨论】:

        猜你喜欢
        • 2011-01-27
        • 2014-03-29
        • 1970-01-01
        • 1970-01-01
        • 2022-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多