Solution · Sports
Building a Sports Analytics Knowledge Graph with Merge
Sports data is notoriously messy. Player names appear differently across scouting databases, transfer market feeds, and broadcast systems. Teams go by abbreviations, nicknames, and full official names depending on the source. When you're building a sports analytics platform, these inconsistencies fragment your data and break the relationships that make it valuable.
In this post, we'll walk through building a complete sports knowledge graph using Merge — from defining entity schemas and ingesting multi-sport data to resolving duplicates and exploring the connected graph of players, teams, leagues, brands, and venues.

The Challenge: Fragmented Sports Data
Consider these real-world scenarios:
- A transfer feed lists "C. Ronaldo" while your scouting database has "Cristiano Ronaldo"
- One API returns "Man Utd" and another uses "Manchester United"
- A broadcast partner sends "FC Barcelona" but your internal system stores "Barcelona"
- An endorsement deal references "Nike Inc" while the sponsorship database just says "Nike"
Without entity resolution, each of these creates orphan records that disconnect your knowledge graph. Merge solves this with configurable fuzzy matching, deterministic deduplication, and a human review queue for edge cases.
Step 1: Define Entity Schemas
Our sports knowledge graph needs six entity types. Each schema defines which attributes are used for matching and how they contribute to confidence scoring.
# Create the Player schema with deterministic matching on external_id
curl -X POST https://merge-ai.app/v1/schemas \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Player",
"attributes": [
{"name": "full_name", "type": "string", "required": true, "identity": true},
{"name": "position", "type": "string"},
{"name": "nationality", "type": "string"},
{"name": "jersey_number", "type": "integer"},
{"name": "external_id", "type": "string", "identity": true}
]
}'
# Create the Team schema
curl -X POST https://merge-ai.app/v1/schemas \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Team",
"attributes": [
{"name": "name", "type": "string", "required": true, "identity": true},
{"name": "city", "type": "string"},
{"name": "founded_year", "type": "integer"},
{"name": "nickname", "type": "string"}
]
}'
We defined schemas for all six entity types:
| Entity Type | Key Attribute | Supporting Attributes |
|---|---|---|
| Sport | name | governing_body |
| League | name | country, sport_level |
| Team | name | city, founded_year, nickname |
| Player | full_name | position, nationality, jersey_number, external_id |
| Brand | name | industry, headquarters |
| Venue | name | city, capacity |

Step 2: Define Relationships
Relationships give structure to the knowledge graph. We registered six relationship types connecting our entities:
# Player plays for a Team
curl -X POST https://merge-ai.app/v1/relationships \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"relationship_type": "plays_for",
"from_entity_type": "Player",
"to_entity_type": "Team"
}'
# Brand endorses a Player
curl -X POST https://merge-ai.app/v1/relationships \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"relationship_type": "endorses",
"from_entity_type": "Brand",
"to_entity_type": "Player"
}'
The full relationship model:
Sport ← belongs_to ← League ← plays_in ← Team ← plays_for ← Player
↑ ↑
sponsors │ endorses │
Brand ────────────────┘
Team ── home_venue ──→ Venue
Step 3: Ingest Multi-Sport Data
We ingested records spanning four major sports — Football, Cricket, Basketball, and American Football — using batch ingestion:
# Batch ingest teams
curl -X POST https://merge-ai.app/v1/entities/batch \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Team",
"records": [
{"attributes": {"name": "Manchester United", "city": "Manchester", "founded_year": 1878, "nickname": "Red Devils"}},
{"attributes": {"name": "Barcelona", "city": "Barcelona", "founded_year": 1899, "nickname": "Blaugrana"}},
{"attributes": {"name": "Real Madrid", "city": "Madrid", "founded_year": 1902, "nickname": "Los Blancos"}},
{"attributes": {"name": "Mumbai Indians", "city": "Mumbai", "founded_year": 2008, "nickname": "MI"}},
{"attributes": {"name": "LA Lakers", "city": "Los Angeles", "founded_year": 1947, "nickname": "Lakers"}},
{"attributes": {"name": "Golden State Warriors", "city": "San Francisco", "founded_year": 1946, "nickname": "Warriors"}},
{"attributes": {"name": "Dallas Cowboys", "city": "Dallas", "founded_year": 1960, "nickname": "Cowboys"}}
]
}'
# Batch ingest players
curl -X POST https://merge-ai.app/v1/entities/batch \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entity_type": "Player",
"records": [
{"attributes": {"full_name": "Lionel Messi", "position": "Forward", "nationality": "Argentina", "jersey_number": 10, "external_id": "PLAYER-001"}},
{"attributes": {"full_name": "Cristiano Ronaldo", "position": "Forward", "nationality": "Portugal", "jersey_number": 7, "external_id": "PLAYER-002"}},
{"attributes": {"full_name": "Virat Kohli", "position": "Batsman", "nationality": "India", "jersey_number": 18, "external_id": "PLAYER-003"}},
{"attributes": {"full_name": "LeBron James", "position": "Forward", "nationality": "United States", "jersey_number": 23, "external_id": "PLAYER-005"}},
{"attributes": {"full_name": "Stephen Curry", "position": "Guard", "nationality": "United States", "jersey_number": 30, "external_id": "PLAYER-006"}},
{"attributes": {"full_name": "Patrick Mahomes", "position": "Quarterback", "nationality": "United States", "jersey_number": 15, "external_id": "PLAYER-007"}}
]
}'

Step 4: Test Entity Resolution
This is where Merge shines. We ingested duplicate records with varying names and formats to test the resolution engine:
# Abbreviated name variant
curl -X POST https://merge-ai.app/v1/entities \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"entity_type":"Player","attributes":{"full_name":"C. Ronaldo","position":"Forward","nationality":"Portugal","jersey_number":7}}'
# Team abbreviation
curl -X POST https://merge-ai.app/v1/entities \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"entity_type":"Team","attributes":{"name":"Man Utd","city":"Manchester","founded_year":1878,"nickname":"Red Devils"}}'
# Name ordering variant
curl -X POST https://merge-ai.app/v1/entities \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"entity_type":"Team","attributes":{"name":"FC Barcelona","city":"Barcelona","founded_year":1899,"nickname":"Blaugrana"}}'
Resolution Results
| Test Case | Input A | Input B | Confidence | Resolution |
|---|---|---|---|---|
| 1 | Cristiano Ronaldo | C. Ronaldo | 0.90 | Auto-merged |
| 2 | Manchester United | Man Utd | 0.72 | Review → Accepted |
| 3 | Barcelona | FC Barcelona | 0.90 | Auto-merged |
| 4 | LeBron James | LeBron James (exact dup) | 1.00 | Auto-merged (deterministic) |
| 5 | Nike | Nike Inc | 0.95 | Auto-merged |
| 6 | Lionel Messi | Leo Messi | 0.72 | Review → Accepted |
Merge uses three resolution bands:
- Auto-merge (≥0.9): High-confidence matches are merged automatically without human intervention
- Review queue (0.7–0.9): Moderate-confidence matches go to a human review queue
- No match (<0.7): Low-confidence pairs remain separate entities
Step 5: Human Review Queue
When the confidence score falls between the review threshold and auto-merge threshold, Merge routes the match to a review queue. This gives domain experts the final say on ambiguous cases.

In our test, two matches landed in the review queue:
-
"Man Utd" ↔ "Manchester United" (confidence: 0.72) — The abbreviation "Utd" has low string similarity with "United", but supporting attributes (city, founded_year, nickname) all matched perfectly.
-
"Leo Messi" ↔ "Lionel Messi" (confidence: 0.72) — "Leo" is a nickname, not a substring of "Lionel". The engine correctly flagged this for review despite matching nationality, position, and jersey number.
After accepting both reviews, each entity consolidated to a single golden record with two source records attached:
# Accept a review
curl -X POST https://merge-ai.app/v1/reviews/{review_id}/accept \
-H "X-API-Key: YOUR_API_KEY"
Step 6: Explore the Knowledge Graph
Once entities are resolved, you can traverse the graph to answer complex queries:
# Get entity sources (see all raw records that merged)
curl https://merge-ai.app/v1/entities/{entity_id}/sources \
-H "X-API-Key: YOUR_API_KEY"

Searching the Graph
Merge provides full-text search across all entity types:
# Search for entities
curl "https://merge-ai.app/v1/entities/search?q=Ronaldo&entity_type=Player" \
-H "X-API-Key: YOUR_API_KEY"

Architecture Overview
The complete data pipeline looks like this:
┌─────────────────────────────────────────────────────────┐
│ Data Sources │
├──────────┬──────────┬───────────┬──────────┬───────────┤
│ Scouting │ Transfer │ Broadcast │ Sponsors │ Ticketing │
│ DB │ Market │ Feeds │ CRM │ System │
└────┬─────┴────┬─────┴─────┬─────┴────┬─────┴─────┬─────┘
│ │ │ │ │
└──────────┴─────┬─────┴──────────┴───────────┘
▼
┌─────────────────┐
│ Merge Ingest │
│ (API / Batch) │
└────────┬────────┘
▼
┌─────────────────┐
│ Resolution │
│ Engine │
├─────────────────┤
│ • Fuzzy match │
│ • Deterministic │
│ • Attr overlap │
└────┬───────┬────┘
│ │
≥0.9 │ │ 0.7-0.9
┌─────────┘ └──────────┐
▼ ▼
┌───────────┐ ┌──────────────┐
│Auto-Merge │ │ Review Queue │
└─────┬─────┘ └──────┬───────┘
│ │
└────────────┬────────────┘
▼
┌─────────────────┐
│ Golden Record │
│ + Source Graph │
└─────────────────┘
Tips for Sports Entity Resolution
-
Use deterministic IDs when available. If your data sources share a common player ID (like FIFA ID, ESPN ID, or a league registration number), mark it as an
identityattribute. This guarantees exact deduplication regardless of name variations. -
Supporting attributes matter. A name match alone may not be sufficient — "Real Madrid" and "Real Sociedad" both start with "Real". Supporting attributes like
city,founded_year, andnicknamedisambiguate fuzzy name matches. -
Expect abbreviations. Sports data is full of abbreviations: "Man Utd", "CSK", "MSG", "GS Warriors". The fuzzy matcher handles many of these, but consider adding aliases for very common abbreviations.
-
Separate entity types prevent false positives. A venue called "Old Trafford" and a team associated with "Old Trafford" won't accidentally merge because they live in different schema namespaces (Venue vs Team).
-
Review thresholds should reflect your domain. In sports, nicknames are extremely common ("Leo", "CR7", "King James"). Setting the review threshold at 0.7 catches these nickname-based matches without flooding the queue with noise.
-
Batch ingest for bulk data, single ingest for real-time. Use
/v1/entities/batchwhen loading historical data and/v1/entitiesfor live event feeds.
API Reference
| Endpoint | Method | Purpose |
|---|---|---|
/v1/schemas |
POST | Define entity type schema |
/v1/relationships |
POST | Register relationship type |
/v1/entities/batch |
POST | Bulk record ingestion |
/v1/entities |
POST | Single record ingestion |
/v1/entities/search |
GET | Full-text entity search |
/v1/entities/{id} |
GET | Get golden record |
/v1/entities/{id}/sources |
GET | View source records |
/v1/entities/{id}/graph |
GET | N-hop graph traversal |
/v1/reviews |
GET | List pending reviews |
/v1/reviews/{id}/accept |
POST | Accept merge review |
/v1/reviews/{id}/reject |
POST | Reject merge review |

Conclusion
Building a sports analytics knowledge graph requires more than just storing data — it requires resolving the messy reality of how that data arrives. Player names, team abbreviations, and brand variations all create fragmentation that undermines analytics and graph queries.
With Merge, we built a complete multi-sport knowledge graph that:
- Resolved 6 duplicate scenarios across players, teams, and brands
- Auto-merged 4 high-confidence matches without human intervention
- Routed 2 ambiguous cases to the review queue for expert decision
- Connected entities across sports, leagues, teams, players, brands, and venues through typed relationships
The combination of fuzzy matching, deterministic deduplication, supporting attribute overlap, and configurable confidence thresholds gives you precise control over how aggressively duplicates are merged — balancing automation with accuracy.
Start building your own sports knowledge graph at merge-ai.app.