Research Nexus 是用於探索 paper、researcher、venue、institution、topic、citation 與 coauthor trend 的資料庫。這個 schema 困難的原因不是有 11 個 table,而是真實 source data 不願意遵守最初 diagram 裡那些乾淨分類。
最後實作的結構
MySQL 8 schema 有五個 entity table 與六個 relationship table:
venue ──< paper >── paper_topic ──> topic
│
├──< paper_author >── researcher
├──< cites >───────── paper
│ │
└──────── coauthor ─────┘
researcher ──< researcher_institution >── institution
researcher ──< interested_in >─────────── topic
核心資料有 3,000 篇 paper、9,404 位 researcher、10,469 筆 paper-author relationship 與 19,218 組 derived coauthor pair。Controlled topic vocabulary 刻意只有 19 列。Citation table 只有 333 條 edge,因為兩端 paper 都必須剛好落在選出的 3,000 篇 subset 內。
最後一點很重要:table 小不代表 load 失敗;預期大小要從 sampling rule 推導。
真實資料逼我們做的決定
Identity 是 source-system contract
Paper 與 researcher ID 來自 source dataset,而不是 name。Name 是 label,不是 stable key:不同 researcher 可能同名,同一 researcher 也可能用不同拼法發表。
Google Scholar attribute 是 optional enrichment。讓 scholar_author_id、citation count 與 email domain 保持 nullable,才能分清楚「未知」與「零」,也避免 enrichment source 意外變成 identity owner。
Free text 需要 controlled vocabulary
Raw keyword 有 spelling、punctuation、casing variant,因此被 mapping 到 19 個 canonical CS topic。這讓 grouping 與 navigation 更穩定,卻也刻意丟失細節。若需要 reproducibility,應保留 raw term 與 mapping version;canonical category 不該抹掉 provenance。
Institution string 更麻煩,因為 source 常放完整 postal address。從地址中擷取看似 institution 的片段是 heuristic,不是事實。這類 transformation 應產生 confidence 與 rejection record,而不是安靜地假裝確定。
Relationship 本身也可能有資料
paper_author 不只是 bridge;author_position 屬於 authorship relationship。Paper 本身沒有單一 author position,researcher 也沒有。
coauthor 將無方向 pair canonical 地保存為 id1 < id2。沒有這條規則,(A, B) 與 (B, A) 會把同一關係存兩次。第二版應用 check constraint 把 ETL convention 變成 database invariant。
一個值得記住的 ingestion bug
CSV 的 quoted field 可以包含 newline,因此 physical line 可能多於 logical record。用 line-oriented shell tool 算行數,再跟 imported row 比較,曾出現 1,505 條 physical line 對 1,441 筆 parsed venue record 的表面差異。
Importer 不一定掉資料;兩個工具只是計算不同單位。
白話來說: CSV 是一套 grammar,不是「一行等於一筆」。計算 CSV record 要用真的 CSV parser。
因此驗證方式也要改:比較 parser record、staged row、accepted row 與明確 rejected row,而不是 physical line。
Index 要跟著 query 方向走
(paper_id, researcher_id) composite primary key 適合從 paper 找 author;它不會自動讓只用 researcher_id 的 lookup 變快,因為 leftmost column 不同。對 researcher ranking 或 topic filtering,可補反方向 access path:
CREATE INDEX idx_pa_researcher_paper
ON paper_author (researcher_id, paper_id);
CREATE INDEX idx_pt_topic_paper
ON paper_topic (topic_id, paper_id);
重點不是「每個 foreign key 都建 index」,而是沿真實 join 與 filter 追 access path,再以 realistic cardinality 和 EXPLAIN 驗證。
第二版我會修改什麼
- 加 staging table,分開 parsing、normalization 與 relational loading。
- 保留 raw value、mapping version、rejection reason 與 source timestamp。
- 用 constraint 強制 coauthor canonical order 與合法 range。
- 把 derived
coauthor、interested_in視為帶 lineage、可重建的 product。 - 從具名 application query 設計 index,量測後移除 redundant index。
- 每個 graph metric 同時報 subset coverage,避免把 sparse induced graph 誤認為完整 citation network。
複習卡
- Real ID 應來自 identity-owning source,不是 display name。
- Nullable enrichment 與值為零不同。
- Controlled vocabulary 改善分析,但應保留 raw provenance。
- Relationship attribute 應放在 junction table。
- CSV record count 需要真正的 parser。
- Composite index 順序要符合 query 的 leading access path。