Research Nexus was designed as a discovery and trend-analysis database for papers, researchers, venues, institutions, topics, citations, and coauthor relationships. The schema was not difficult because it had eleven tables. It was difficult because real source data refused to obey the clean categories drawn in the first diagram.
The implemented shape
The MySQL 8 schema contained five entity tables and six relationship tables:
venue ──< paper >── paper_topic ──> topic
│
├──< paper_author >── researcher
├──< cites >───────── paper
│ │
└──────── coauthor ─────┘
researcher ──< researcher_institution >── institution
researcher ──< interested_in >─────────── topic
The core held 3,000 papers, 9,404 researchers, 10,469 paper-author relationships, and 19,218 derived coauthor pairs. A controlled topic vocabulary had only 19 rows by design. The citation table had 333 edges because both endpoints had to exist inside the selected 3,000-paper subset.
That last point matters: a small table is not automatically a failed load. Its expected size follows from the sampling rule.
What the data forced us to decide
Identity was a source-system contract
Paper and researcher identifiers came from the source dataset rather than from names. Names are labels, not stable keys: two researchers can share a name, and one researcher can publish under several spellings.
Google Scholar attributes were optional enrichment. Keeping scholar_author_id, citation count, and email domain nullable preserved the difference between “unknown” and “zero.” It also prevented the enrichment source from becoming the identity owner.
Free text needed controlled vocabularies
Raw keywords had spelling, punctuation, and casing variants. They were mapped into 19 canonical computer-science topics. This improved grouping and navigation, but deliberately lost detail. The correct design is to retain the raw term and mapping version when reproducibility matters; a canonical category should not erase provenance.
Institution strings were even harder because the source often contained postal addresses. Extracting the segment that looked like an institution was a heuristic, not a fact. Such transformations should produce confidence and rejection records rather than silently claiming certainty.
Relationships sometimes carry data
paper_author was more than a bridge: author_position belonged to the authorship relationship. A paper does not have one author position, and a researcher does not have one either.
The coauthor table stored each undirected pair canonically as id1 < id2. Without that rule, (A, B) and (B, A) would represent the same relationship twice. A database check constraint would make this ETL convention enforceable.
One ingestion bug worth remembering
A CSV file can have more physical lines than logical records because a quoted field may contain a newline. Counting with a line-oriented shell tool and comparing that number to imported rows produced an apparent discrepancy: 1,505 physical lines versus 1,441 parsed venue records.
The database importer was not necessarily dropping data. The two tools were counting different units.
In plain terms: CSV is a grammar, not “one line equals one row.” Use a CSV parser to count CSV records.
This changed the validation method. Instead of comparing physical line counts, the pipeline should compare parser record counts, staged rows, accepted rows, and explicitly rejected rows.
Indexes follow query direction
A composite primary key on (paper_id, researcher_id) supports lookup by paper. It does not automatically make lookup by researcher_id efficient because the leftmost column differs. Queries that rank researchers or filter by topic therefore need indexes in the reverse access direction, such as:
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);
The lesson is not “index every foreign key.” It is to trace actual joins and filters, then verify them with EXPLAIN and realistic cardinalities.
What I would change in a second version
- Add staging tables so parsing, normalization, and relational loading are separate phases.
- Preserve raw values, mapping versions, rejection reasons, and source timestamps.
- Add enforceable constraints for canonical coauthor order and controlled ranges.
- Treat derived
coauthorandinterested_inrows as rebuildable products with lineage. - Design indexes from named application queries, then remove redundant ones after measurement.
- Report subset coverage beside every graph metric so a sparse induced citation graph is not mistaken for the full network.
Review card
- Real identifiers should come from identity-owning sources, not display names.
- Nullable enrichment is different from a zero value.
- Controlled vocabularies improve analysis but should preserve raw provenance.
- Relationship attributes belong on junction tables.
- CSV record counts require a real parser.
- Composite-index order must match the query’s leading access path.
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。