2014-01-22 108 views
1

我正嘗試使用BulkCopy.WriteToServer()從Oracle向SQL Server執行1000萬行的批量插入操作。C#SqlBulkCopy錯誤恢復錯誤

我已經確定

  • 表列和數據類型的兩側是相同的。我的意思是Oracle的Date數據類型映射到Sql Server的日期時間數據類型。 VARCHAR2映射到VARCHAR等
  • 沒有觸發器和索引的目標表

當它來到剛剛約1.4萬行未能與System.ArgumentOutOfRangeException:時,分,秒參數描述了一個未 - 可表示日期時間。在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 block中做任何事情讓它繼續下去?我不在乎留下不良數據。我正在尋找一種方法來告訴它繼續出現插入錯誤。
  2. 有什麼辦法可以防止這種情況發生?例如,我可以在填充OracleDataReader的選擇查詢中做什麼?
  3. 如果上述兩個問題沒有解決方案,那麼有沒有什麼好方法可以清除Oracle端的「壞日期」?

感謝,

更新: 我做了以下

  1. 更改目標表中的數據類型從日期時間到DATETIME2
  2. 修改選擇列表

    CASE WHEN my_date_column < To_Date('01/01/1753','mm/dd/yyyy')THEN To_Date('01/01/1753','mm/dd/yyyy ')ELSE my_date_column END

    對於具有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查詢快速發現這些違規值? 還有什麼建議嗎?

回答

1

Oracle數據庫可以在Julian時代存儲日期,範圍從公元前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. 見上。
+0

我更新了我的進一步信息的問題。需要進一步幫助。 – Shawn

+0

「OracleDataReader.GetDateTime」中出現錯誤的事實表明SQL Server與它無關。閱讀時發生錯誤。 .NET DateTime(它與datetime2具有完全相同的範圍)無法表示某個日期值。在Oracle查詢工具中執行查詢並檢查結果。一定有一些錯誤。您可以按以下方式平分錯誤行:將查詢更改爲僅返回第1M行。如果錯誤再次發生,則它位於第一個1M行。如果不是,他們很清楚。排除它們並再次平分。找到有故障的行。 – usr

+0

我明白了。感謝您的幫助。我很感激。 – Shawn

0

確定。我知道了。我正在回答我的第二和第三個問題。

這是Oracle的錯誤的日期,看起來像這樣'01 /二千○六分之二十六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) 

這清理壞數據。

0

這個問題很好地由「usr」的答案來描述。您可以在具有問題日期的源Oracle列源上執行CASE語句,以將無效日期轉換爲NULL或默認值。現在,識別問題的行或列是一個大問題。我有一個方法來確定問題。

請閱讀我的這篇博客,以找出問題的列,以便你可以做適當的DECODE轉換的問題可以追溯到NULL或有效的默認 https://sqljana.wordpress.com/tag/datetime-odp-net-oracle/