需將來源資料庫(客戶端)資料同步至目標資料庫(接口表),要求「已存在不重複導入、不存在則新增」。本文示範在 C# 中拼接 Oracle MERGE / INSERT WHEN NOT EXISTS 動態 SQL,並解決日期、數字、NULL 值拼接陷阱。
CREATE TABLE A (
ID NUMBER(20),
UPDATED_BY NUMBER(20),
UPDATE_DATE DATE,
UPDATE_LOGIN NUMBER(20),
CREATION_DATE DATE,
CREATED_BY NUMBER(20),
NAME VARCHAR2(60),
IMPORT_STATE VARCHAR2(1)
);
INSERT WHEN (NOT EXISTS (SELECT 1 FROM A WHERE A.ID = :id))
THEN INTO A (ID, UPDATED_BY, CREATED_BY, UPDATE_DATE, UPDATE_LOGIN, CREATION_DATE, NAME, IMPORT_STATE)
VALUES (:id, :ub, :cb, TO_DATE(:ud, 'YYYY-MM-DD HH24:MI:SS'), :ul, TO_DATE(:cd, 'YYYY-MM-DD HH24:MI:SS'), :name, :state)
SELECT 1 FROM dual;
WHEN NOT EXISTS 確保冪等性(重複執行不重複寫入)。SELECT 1 FROM dual 滿足 Oracle INSERT ... WHEN 語法要求的子查詢。foreach (DataRow dr in dsOra.Tables[0].Rows)
{
string str = $@"
INSERT WHEN (NOT EXISTS (SELECT 1 FROM A WHERE A.ID = '{dr["ID"]}'))
THEN INTO A (ID, UPDATED_BY, CREATED_BY, UPDATE_DATE, UPDATE_LOGIN, CREATION_DATE, NAME, IMPORT_STATE)
VALUES (
'{dr["ID"]}',
'{dr["UPDATED_BY"]}',
'{dr["CREATED_BY"]}',
TO_DATE('{Convert.ToDateTime(dr["UPDATE_DATE"]):yyyy-MM-dd HH:mm:ss}', 'YYYY-MM-DD HH24:MI:SS'),
'{dr["UPDATE_LOGIN"]}',
TO_DATE('{Convert.ToDateTime(dr["CREATION_DATE"]):yyyy-MM-dd HH:mm:ss}', 'YYYY-MM-DD HH24:MI:SS'),
'{dr["NAME"].ToString().Replace("'", "''")}',
'{dr["IMPORT_STATE"]}'
)
SELECT 1 FROM dual;";
sb.Append(str);
sb.AppendLine(";");
}
VALUES ( )VALUES ( , , , , , , , )VALUES ( '', '', '', '', '', '', '', '' )VALUES ( ''+'', ''+'', ... )VALUES ( '' + id + '', '' + ub + '', ... )| 陷阱 | 現象 | 對策 |
|---|---|---|
| 數字欄位 NULL | 直接拼接數字變數若為 null 會產生 VALUES ( , ...) 語法錯誤 | 統一以字串形式拼接,NULL 時拼入 NULL 關鍵字或空字串再 TO_NUMBER |
| 日期格式 | 隱式轉換依賴 NLS 設定,跨環境易失敗 | 強制 TO_DATE('yyyy-MM-dd HH24:mi:ss', 'YYYY-MM-DD HH24:MI:SS') |
| 單引號逸出 | 資料含單引號導致 SQL 截斷 | .Replace("'", "''") 雙寫單引號 |
| DUAL 忘寫 | INSERT WHEN ... THEN ... VALUES ... 缺子查詢報錯 | 務必以 SELECT 1 FROM dual 結尾 |
OracleCommand.Parameters.Add 避免注入、自動處理型別轉換、計畫重用。ArrayBindCount + 陣列參數,一次往返寫入成千上萬筆。