🧾BI 報表怎麼全部變成 0? 軟體工程師用一個真實案例,教你看懂「欄位錯誤、Null 與 0 值」到底在吵什麼

一、前言:為什麼我的報表全部變成 0?

對很多非工程背景的同事來說,「BI 報表」就像是魔法:
丟進一堆資料,按一下按鈕,就長出一張很專業的圖表,什麼良率、占比、排名,一眼就懂。

直到有一天——

「咦?怎麼這次產生的報表,所有數字都變成 0,還有一堆空白欄?」
「系統是不是壞掉了?」

其實,很多時候不是「系統壞了」,而是:

  1. 欄位名稱改過了,但報表還在找舊名字

  2. 資料裡有大量 Null 或 0,公式又寫錯位置

  3. 轉表(Pivot)後,欄位跟你以為的不一樣

這篇文章,我會用一個實際的「工程師除錯過程」當故事,
但把技術細節改寫成適合一般人閱讀的版本,
讓你可以聽得懂工程師在說什麼,也比較知道問題到底出在哪裡。


二、故事背景:一個「良率儀表板」的日常

想像有一個公司,用 BI 工具做了一個「良率儀表板」,
每天會把測試資料轉成表格,算出各種:

  • 每個批次(批號)的 測試總數

  • 每個不良原因(BIN)的 發生次數與百分比

  • 各家代工廠、產品型號、日期…的 良率

後端流程大概長這樣(用很白話的說法):

  1. 從資料庫抓出原始測試資料 → 放到一張叫 TestData 的表

  2. 把每個測試點的 bin 數量彙總 → 變成 BinSummary

  3. 再做一次彙總(Pivot),讓每個 BIN 變成欄位(BIN1、BIN2、BIN3…)

  4. 最後算出各個 BIN 的占比,也就是常看到的 % 欄位

整個過程會用一支腳本(Script)自動完成,
在儀表板上就會看到一個按鈕叫「Generate Yield Summary」之類的東西。

平常都好好的,直到有一天——

  • 上面的總表又長出來了,但

  • 底下的圖表突然消失

  • 表格裡一堆 BIN 欄位全部是 0.0 或空白

  • 再按一次按鈕,跳出錯誤訊息說:「欄位不存在」

聽起來很可怕?其實蠻常見的 😅


三、第一個症狀:欄位名稱錯誤,「欄位不存在」是什麼意思?

系統先丟出第一個錯誤訊息:

The column 'LOT_ID' does not exist in the collection.
(欄位 LOT_ID 不存在)

簡單翻譯:

  • 某段程式在說:「請幫我用 LOT_ID 這欄排序」

  • 但現在這張表裡,根本沒有叫 LOT_ID 的欄位

  • 就像你跟同事說「請把檔案放進 ‘藍色夾子’」,但桌上只有紅色跟綠色夾子一樣

在腳本裡,大概會看到像這樣的程式(示意改寫版):

# 取得報表用的資料表 summary_table = Document.Data.Tables["YieldSummary"] # 嘗試用 LOT_ID 這個欄位排序 summary_table_visual.SortInfos.Clear() summary_table_visual.SortInfos.Add( summary_table.Columns.Item["LOT_ID"], TablePlotColumnSortMode.Ascending )

如果資料表裡沒有 LOT_ID 這欄,腳本就會直接當掉。

✅ 工程師的解法:先檢查欄位存在再用

比較穩的寫法會變成:

summary_table = Document.Data.Tables["YieldSummary"] cols = summary_table.Columns col_names = [c.Name for c in cols] summary_table_visual.SortInfos.Clear() if "LOT_ID" in col_names: summary_table_visual.SortInfos.Add( summary_table.Columns.Item["LOT_ID"], TablePlotColumnSortMode.Ascending ) elif len(cols) > 0: # 沒有 LOT_ID,就選第一欄當排序基準 first_col = cols[0] summary_table_visual.SortInfos.Add( first_col, TablePlotColumnSortMode.Ascending )

對一般讀者可以這樣理解:

先看看桌上有沒有「藍色夾子」
有的話就塞進藍色夾子
沒有就先塞到第一個看到的夾子
不然不要硬回報錯,就能讓流程至少跑完


四、第二個症狀:Datecode vs DATE_CODE —— 大小寫、拼法差一點就掛掉

後來又出現另一個錯誤:

The column 'Datecode' does not exist in the collection.

但工程師看了一下資料表,裡面明明就有一欄叫 DATE_CODE
差在哪?

  • 舊版報表、某些視覺化元件,還在用 Datecode(D 大寫,其餘小寫)

  • 新版資料流程,只留下 DATE_CODE(全部大寫)

  • 當腳本重新載入資料表、把舊欄位砍掉後,
    舊設定還在找 Datecode → 找不到 → 當掉

✅ 工程師的解法:加一個「別名欄位」

這種情況下,最溫和的做法不是到處改設定,而是:

在新的資料表裡,多生一欄「Datecode」,內容=DATE_CODE
讓舊的設定也能順利找到東西。

程式示意(已改名):

report_table = Document.Data.Tables["YieldSummary"] cols = report_table.Columns col_names = [c.Name for c in cols] if "Datecode" not in col_names and "DATE_CODE" in col_names: cols.AddCalculatedColumn("Datecode", "[DATE_CODE]")

白話翻成生活場景:

公司改了部門名稱,
但還是有人習慣說「舊名字」,
那就乾脆在名牌上寫「A 部門(原 B 部門)」,
不然每天都有人找不到人。


五、第三個症狀:為什麼表格裡一堆 0.0,圖表也沒東西?

這是最讓人頭痛的一段:

報表有跑出來、欄位也都在,但:
  • 很多 BIN 欄位全部是 0.0
  • 有些欄位是空白
  • 圖表只剩下少數幾個 BIN,或直接消失

工程師在程式裡看到一段類似這樣的計算(示意版):

# 在彙總後的表格裡直接計算百分比 if "Rate%" in [c.Name for c in cols]: cols.Remove("Rate%") cols.AddCalculatedColumn("Rate%", "[QTY] / [TOTAL_QTY] * 100")

看起來很合理嘛:「比例 = 分子 / 分母 * 100」。
但問題是:這張表根本沒有完整的 QTY / TOTAL_QTY。

實際流程是這樣:

  1. 一開始在明細表 RawData 裡有每個測試點、每個 BIN 的 COUNT

  2. 做一次彙總(Pivot)→ 得到 BIN_COUNT(每個 BIN 的總數)

  3. 又再彙總一次 → 變成報表用的 YieldSummary(一列對應一個批次 + 多個 BIN 欄位)

如果你在「第三階段」才想起來要算 %
卻又用的是「早就遺失或對不上」的 QTYTOTAL_QTY
那算出來的結果就會:

  • 不是 Null 就是 0

  • 再做平均或加總 → 也變成 0

✅ 正確做法:在數據最完整的階段算好 Rate%

比較穩的方法,是在 RawData 或第一次彙總後的表格,用「已知的 BIN 數量」算出 Rate%,例如:

# Step 1:每個批次內,各個 BIN 的數量 # 已有欄位:BIN_COUNT # Step 2:每個批次的總數 cols.AddCalculatedColumn( "TotalCount", "Sum([BIN_COUNT]) OVER ([BatchID])" ) # Step 3:計算每個 BIN 在該批次的百分比 cols.AddCalculatedColumn( "Rate%", "([BIN_COUNT] / [TotalCount]) * 100" )

然後在後面的 Pivot、報表、圖表,通通用這個 Rate% 就好。

這樣做的好處是:

  • % 是在資料還「最乾淨完整」時就算好

  • 後續不管怎麼轉表(Pivot)、彙總、篩選,
    % 都不會因為欄位遺失、Join 失敗而變成 Null 或 0


六、如何只顯示「真的有資料」的 BIN?

接著,工程師還寫了一個小小的幫手函式,
專門用來判斷「這一欄是不是全部都是空或 0,如果是,就不要顯示出來」。

改寫後的版本大概長這樣(已匿名化):

def has_non_zero_value(table, column_name): col = table.Columns[column_name] cursor = DataValueCursor.Create(col) for row in table.GetRows(cursor): value = cursor.CurrentValue # 1. Null 就略過 if value is None: continue # 2. 轉成字串,處理空白 s = str(value).strip() if s == "": continue # 3. 能轉成數字,而且不是 0,就代表這欄「有意義的值」 try: if float(s) != 0: return True except: # 4. 如果連轉數字都失敗,那它至少「不是 0」 return True # 走完整個欄位,都沒有出現非 0 的值 return False

接著在決定要不要把「某個 BIN 欄位」放進報表或圖表時,就可以寫成:

for col in data_table.Columns: if col.Name.startswith("BIN"): if has_non_zero_value(data_table, col.Name): bin_columns.append(col.Name)

對非工程背景的人來說,可以這樣理解:

我不會硬把所有欄位都丟上圖表,
只顯示「這一次真的有數據的 BIN」,
空空的、全部是 0 的就不要擺上去,
讓報表看起來乾淨、有重點。


七、順便把「Rate% 為空或 0 的列」也刪掉

除了整欄都是 0 的 BIN 不顯示,有時候我們也希望:

「這次分析中,某個 BIN 在某個批次完全沒發生,就不要列出來。」

這時可以在「行」的層級再清理一次資料,例如:

table = Document.Data.Tables["YieldDetail"] rate_cursor = DataValueCursor.Create(table.Columns["Rate%"]) rows_to_remove = IndexSet(table.RowCount, False) for row in table.GetRows(rate_cursor): v = rate_cursor.CurrentValue if v is None or str(v).strip() == "": rows_to_remove.AddIndex(row.Index) continue try: if float(str(v).strip()) == 0: rows_to_remove.AddIndex(row.Index) except: rows_to_remove.AddIndex(row.Index) table.RemoveRows(RowSelection(rows_to_remove))

白話說:

幫我把「占比是 0 或空的那一整列」刪掉,
這樣 Pareto 圖就只會顯示真的有貢獻到缺陷數量的 BIN。


八、總結:這整件事,其實都是「欄位 + Null/0 + 時機點」的問題

回頭看這次的完整除錯過程,你會發現關鍵都是:

  1. 欄位名稱變動,但舊設定沒跟著改

    • LOT_ID 找不到

    • Datecode vs DATE_CODE

    • 解法:檢查欄位存在、必要時加「別名欄位」

  2. Rate% 計算放錯地方(在資料不完整的階段才算)

    • 在 Pivot 後的表上用已經對不上的欄位來算

    • 結果大量變成 Null 或 0

    • 解法:在 RawData 或第一階段彙總時就算好

  3. Null 與 0 沒處理好,導致報表很醜、使用者看不懂

    • 空值跟 0 混在一起

    • 報表一堆 0.0,看起來像壞掉

    • 解法:

      • 寫小函式檢查「是否有非 0 的值」

      • 過濾掉 Rate% 空或 0 的列

      • 不顯示完全沒有數據的 BIN 欄


九、寫給非工程師看的小提醒

如果你不是工程師,但常常要看這種 BI 報表,
下次遇到以下狀況,可以比較不慌張:

  • 報表跑得出來,但全都是 0 或空白

  • 圖表突然只剩一兩根柱子,或乾脆消失

  • 操作某個按鈕時跳出「欄位不存在」之類的英文錯誤

你可以跟工程師這樣描述:

  • 「這份報表之前有很多 BIN 欄位,現在只剩幾個,其他欄位都變 0 或空白。」
  • 「我看到錯誤訊息裡寫 'Column XXX does not exist',是不是欄位名稱跟以前不一樣?」
  • 「最近是不是有調整欄位名稱或計算公式?」


這樣比單純說「系統壞掉了」更有幫助,也能讓工程師更快找到癥結點。


十、寫給工程師的備忘清單

最後,給同樣在維護 BI 報表、資料視覺化的工程師一點 checklist:

  • ✅ 資料流程的哪個階段最適合計算 Rate% / KPI?

  • ✅ 每次 Rename 欄位時,有沒有檢查:

    • 計算欄位

    • 視覺化設定(Sort、Color by、Label by)

    • Script / 自動化流程

  • ✅ Pivot 前後,欄位名稱是否有變化(例如 BINBIN (Avg))?

  • ✅ 有沒有統一處理:

    • Null 值

    • 空字串

    • 0 值

  • ✅ 是否只顯示「真的有意義的欄位」、不把全 0 的欄位塞到報表裡?

留言

這個網誌中的熱門文章

🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

🖥️遠端桌面連線完整新手指南:Windows RDP、Chrome Remote Desktop、AnyDesk、TeamViewer 一次搞懂

🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)