先寫 CSV 合約 再談匯出工具
CSV 是交換格式,不是資料庫 schema,也不是 Excel 的型別宣告。工程團隊應先寫欄位規格:欄名、順序、是否必填、單位、編碼、空值表示、引號和換行,再選匯出工具。RFC 4180 描述每筆記錄一行、欄位以逗號分隔、含逗號或換行的欄位用雙引號,並提醒實作之間仍有差異。
本例的泵浦批次檔固定 UTF-8、CRLF、第一行欄名、五欄順序:record_id,device_id,flow_l_min,quality,recorded_at。flow_l_min 是十進位數字字串,quality為本案例自訂交換品質,允許Good、Stale、Bad;與來源品質碼的映射另列,不能宣稱這是通用OPC列舉;空值不寫成 0。這些是本案契約,不能因某個試算表自動轉型就省略。
欄位規格要寫成可審核的契約文件。除了欄名與順序,還應列出編碼、換行、分隔字元、引用字元、空值、數字小數點、時間精度、列舉與唯一性。若資料來自不同國家,不能讓小數逗號與欄位逗號同時出現而未定義;本案例固定小數點為句點、欄位分隔為逗號。
| 欄位 | 型別/單位 | 必填 | 例值與限制 |
|---|---|---|---|
| record_id | 字串 | 是 | B20260917-0001,不可重複 |
| device_id | 字串 | 是 | P-101,保留連字號 |
| flow_l_min | 十進位 L/min | 否 | 120.5;空值代表未量測 |
| quality | 列舉 | 是 | Good/Stale/Bad |
| recorded_at | ISO 8601 offset | 是 | 2026-09-17T10:00:00+08:00 |
引號 逗號與換行的實例
若 device_note 另放入欄位,內容為「濾網,待清潔」時,整個欄位必須被雙引號包住;內容含雙引號時,以兩個雙引號表示一個字元。換行也屬欄位內容,不應簡單以每一行 split 逗號。匯入程式要使用 CSV parser,並測試欄位內逗號、引號、CRLF 和最後一行沒有換行。
例如一筆合法記錄可寫成 B1,P-101,120.5,Good,2026-09-17T10:00:00+08:00。若備註欄為 濾網,待清潔,應寫成 B1,P-101,120.5,Good,"濾網,待清潔",2026-09-17T10:00:00+08:00,同時更新欄位規格為六欄。不可讓文章中的例值與欄位數互相矛盾。
匯出程式應先產生暫存檔,再以 parser 讀回檢查欄數和內容,最後在同一檔案系統且支援原子更名的環境發布;跨磁碟搬移需另定完成標記。這能避免操作員讀到只寫了一半的檔案。每個檔案另產 manifest,記錄 schema_version、row_count、created_at、sha256 和來源批次。manifest 不是 CSV 欄位,但能讓匯入端知道檔案是否完整。
欄位名稱含非 ASCII 字元時,本案例仍建議用穩定的 ASCII machine name,另以資料字典提供中文顯示名。這不是 CSV grammar 的要求,而是便於 SQL、腳本和跨平台系統維護的工程決策,應寫入 schema_version。
版本升級時不要默默改欄位順序。先發布新 schema_version,再讓匯入端接受新舊版本各自的欄位清單;未知版本應拒收並保留檔案。這能把格式變更從一次性的人工提醒變成可重現的流程。
若要支援 tab 分隔或分號分隔,必須另建版本或明確參數,不能讓 parser 自動猜分隔符;猜錯時欄數可能仍然看似合法而造成資料錯欄。
| 輸入片段 | parser 結果 | 是否通過 | 排查 |
|---|---|---|---|
| P-101,"濾網,待清潔" | 一個含逗號欄位 | 是 | 確認欄位數 |
| P-101,"泵""A" | 第二欄內容為泵"A | 是 | 檢查雙引號成對 |
| P-101,120.5 | 欄位不足 | 否 | 補必填欄位或拒收 |
| P-101。 | 空字串 | 依欄位規格 | 不可自動改成 0 |
CSV grammar 不等於 schema
RFC 4180 的 grammar 只說明如何分隔與引用文字,沒有定義 flow 的單位、時間時區、品質列舉或 record_id 唯一性。因此「能被 parser 讀入」不代表資料符合工業交換規格。匯入端要先驗欄位數與欄名,再驗列舉、數字範圍、時間 offset 和跨列唯一鍵。
Excel 或其他試算表可能把 0012 顯示成 12、把長 ID 當數字、把日期依本機 locale 改寫,也可能把空欄當成零。驗收不能只開檔看畫面;應以原始 bytes、parser 結果和匯入後資料庫查詢三者比對。若一定要給人工開啟,另提供 schema 說明與不可變更的原始檔。
資料型別驗證必須保留錯誤的原文。flow_l_min=1O2.3 中的 O 不是零時,錯誤報告要指出列號、欄名、原始字串和期望格式;不能把它轉成 102.3。對空值也要區分「欄位缺少」、「欄位為空字串」和「明確的 NULL 標記」,否則統計程式會把三者混為一談。
檔案大小與列數也要驗收。manifest 宣告 3 筆但 parser 讀到 2 筆時,檔案應拒收;最後一行被截斷或 CRLF 被轉換時,先比較 hash 與 row_count,再查傳輸模式,不要直接修補來源檔後匯入。
數字欄的量程也要有規格,例如 flow_l_min 允許 0 到 500、最多 3 位小數。超過量程、負值或 NaN 字串要回報原始列,不可在匯入時裁切到上下限,因為裁切會掩蓋設備或轉換錯誤。
| 檢查層 | 輸入 | 判定 | 失敗例 |
|---|---|---|---|
| 語法 | CRLF、引號、欄數 | parser 可讀 | 未閉合引號 |
| schema | 欄名/順序/列舉 | 符合契約 | quality=OK |
| 語意 | 單位/範圍/時間 | 可供控制報表使用 | flow=-4 |
| 保存 | raw bytes/hash | 可追溯 | Excel 另存覆寫 |
匯出 匯入與錯誤回報流程
以 3 筆資料做驗收:第一筆 Good=120.5,第二筆 Stale 且 flow 空值,第三筆 Bad 且仍保留原始數值 118.0。匯出後計算檔案 hash;匯入端先解析到暫存表,逐列回報 record_id、欄位、錯誤原因,全部通過才提交正式表。第二筆空值在此契約合法,不應跳過;另加入第四筆負值作錯誤測試,亦不能把 Bad 改為 Good。
排錯順序是先看編碼與原始 bytes,再看 parser 欄位數,接著看 schema 驗證,最後才檢查 Excel 顯示。若匯入後時間少了 offset,應回到 raw CSV 確認;若只有 Excel 顯示不同而 parser 正確,問題是工具格式化,不是來源資料。版本欄可放在檔名或伴隨 manifest,但不能用檔名取代欄位契約。
如果交付對象使用 Excel,應把它當檢視工具而非權威 parser。驗收先以固定語言的 CSV parser 讀原檔,確認 bytes 與 hash;再開啟副本觀察是否顯示科學記號、日期轉換或前導零消失。若使用者需要可編輯表格,另交付 XLSX 並保留原始 CSV,不能以另存 CSV 取代來源。
RFC 4180 是 Informational 文件,記錄常見 CSV 格式並非所有產品的完整標準。本文因此把分隔、引號和換行當 grammar,把單位、品質和版本當自訂 schema,兩層在表格與測試中分開。
| 筆次 | 原始值 | 預期匯入 | 驗收結果 |
|---|---|---|---|
| 1 | 120.5,Good | 120.5 / Good | 接受 |
| 2 | 空值,Stale | NULL / Stale | 接受並保留品質 |
| 3 | 118.0,Bad | 118.0 / Bad | 接受但不可供控制 |
| 4 | -4,Good | 範圍錯誤 | 拒收並回報列號 |
FAQ 限制與官方來源
匯入提交要有批次狀態:Parsed、Validated、Committed、Rejected。任何一列驗證失敗就進 Rejected,並保存錯誤清單;若資料庫提交失敗,整批回滾但不得刪除原始檔與 manifest。相同來源與schema版本下,重跑已成功提交的同一hash應回報已處理;上次失敗則允許按修正後規則重新驗證,避免把重複匯入誤當新批次。
跨平台測試至少包括 Linux parser、Windows 匯入工具和資料庫 COPY 或等價 API。三者讀同一份原始檔後,欄位數、Unicode、空值與時間offset解讀必須相同,輸入原檔hash也須一致;若工具差異無法消除,就要在交付文件中明示限制。
對含逗號的備註做 round trip:原始字串送入 exporter、parser、資料庫再輸出,應得到同樣字元與欄位位置。對含雙引號和換行的備註各做一次,並比較解析後的欄位內容;重新匯出的引號與換行可能合法改變,只有要求位元組完全不變的原檔傳輸才比較相同hash;若只在 Excel 畫面看起來相同,仍不足以通過。
資料字典要把每欄的 nullability、unit、scale 和 enum 寫清楚,並附一個合法檔與四個非法檔供工具測試。任何工具更新都重新跑同一組 fixture,避免匯入行為隨版本改變卻無人發現。
FAQ1:CSV 能否直接當 schema?不能,CSV grammar 不含單位、列舉、唯一鍵或時間語意。
FAQ2:把數字欄全部加雙引號就能避免 Excel 轉型嗎?不能,引用只處理分隔語意;開啟工具仍可能依 locale 轉型。
FAQ3:空值可否寫成 0?只有欄位規格明確定義才可以;量測未知時 0 會製造錯誤資料。
FAQ4:最後一筆一定要 CRLF 嗎?RFC 4180 描述最後記錄可有或沒有換行,但本案例為可重現驗收,明確要求 CRLF。
限制:本文使用 RFC 4180 的常見規則與自訂工業 schema,未指定某一 PLC、資料庫或試算表產品的自動型別行為。正式交付前要以目標 parser、編碼和匯入設定測試。
參考:IETF RFC 4180, Common Format and MIME Type for CSV Files(定義 record、CRLF、逗號與雙引號規則)