C#SqlBulkCopy恢复出错

时间:2014-01-22 12:19:33

标签: c# sql-server oracle etl

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

我确定了

  • 两侧的表格列和数据类型相同。我的意思是Oracle的Date数据类型映射到Sql Server的日期时间数据类型。 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方面的“糟糕日期”?
  4. 谢谢,

    更新: 我做了以下

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

      当my_date_column< To_Date('01 / 01/1753','mm / dd / yyyy')那么To_Date('01 / 01/1753','mm / dd / yyyy')ELSE my_date_column END

      表示所有具有DATE数据类型的列。

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

      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查询快速发现这些违规值? 还有其他建议吗?

3 个答案:

答案 0 :(得分: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. 见上文。

答案 1 :(得分:0)

行。我明白了。我正在回答第2和第3个问题。

这是甲骨文的糟糕日期,看起来像这个'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)

这可以清理坏数据。

答案 2 :(得分:0)

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

请阅读我的博客文章以确定问题列,以便您可以执行相应的DECODE将问题日期转换为NULL或有效默认值 https://sqljana.wordpress.com/tag/datetime-odp-net-oracle/