A name is a label, not a key
Every horse has a name. It is tempting to treat that name as the identifier. It is printed on every racecard, every result and every form line.
It is also the first thing that breaks in a racing database.
Same name horses exist. Two different animals can carry the same name in different countries, in different decades, or even in the same season. If your pipeline joins on name, it will quietly merge their careers into one.
This article looks at that single problem: what it does to data, why it is hard to spot, and how to test whether a source handles it properly.
How same name horses appear in real data
Racing names are reused for ordinary reasons:
- A name becomes available again after a horse has been retired for long enough.
- A horse is registered in one country and a different horse with the same name is registered in another.
- Names are short, and owners and breeders like familiar words.
- Sources add suffixes or drop them, so the same horse can appear under two spellings.
The last point matters as much as the first. The problem runs in both directions. Two horses can collapse into one record, and one horse can split into two.
Both errors are silent. Nothing throws an exception. The table loads, the row counts look plausible, and the model trains.
What a merged record does to your work
Suppose two different horses share a name and your join treats them as one. Several things go wrong at once.
Career statistics are blended
Runs, wins, days since last run and average distance are all calculated across two animals. A feature such as “runs in the last 90 days” becomes meaningless for that name. No single horse ever had that history.
Rest and layoff features are distorted
Gaps between runs are a common model feature. When two careers are interleaved, the gaps shrink or vanish. A horse that has not raced for a year can look as if it ran last month.
Backtests leak
If one of the two animals ran later than the other, the merged history can feed future information into an earlier row. A backtest then looks cleaner than the method deserves. This is the kind of flaw that only appears once the model meets new data.
Dashboards mislead
An analytics team building a trainer or owner dashboard may attribute a horse’s runs to the wrong yard. The chart looks fine. The conclusion is wrong.
None of these errors announce themselves. That is why they last.
Why a busy day makes it harder to see
A busy day can carry more than 40 races and more than 400 runners across UK and Irish courses. At that volume, nobody inspects rows by eye.
Over a long history the numbers are far larger. HRDB holds 2.2M result records across 226K races and 173K horses. At 173K horses, a handful of repeated names is not an edge case. It is a certainty.
So the question is not whether your data contains same name horses. It does. The question is whether your source separates them before you ever see the file.
The related failure: one horse in two places
Name collisions have a close relative. If two horses are merged, the merged record can appear to run in two races on the same day. That is physically impossible for one animal.
This makes it a useful signal. A horse assigned to two races on the same day is almost always a symptom of an identity error somewhere upstream.
It is one of the automated integrity gates that every nightly update at HRDB must pass before it is published:
- No horse assigned to two races on the same day.
- No duplicate runner rows.
- No missing horse, jockey or trainer IDs.
- Separation of different horses that share a name.
This piece is about the last gate, and how it connects to the first. If you want the full overview of all four, our earlier article on nightly checks covers it.
Why identifiers do the real work
The fix is not a cleverer way of matching names. The fix is to stop matching on names at all.
A stable identifier gives each horse one key that does not change. Names, spellings and suffixes can vary around it. The key stays fixed.
In practice that means:
- Join horses to results on the horse ID, never on the name.
- Store the ID in your own tables from the first day.
- Treat the name as display text only.
- Do the same for jockeys and trainers.
This is also why a missing ID is treated as a failure. A runner row without a horse, jockey or trainer ID cannot be joined safely. It is better that the update is stopped than that the gap reaches your pipeline.
UK and Hong Kong data live in separate, isolated schemas. That separation means identifiers are scoped to the schema they belong to. Do not assume an ID or a name in one schema refers to anything in the other.
How to test any source for name collisions
You do not need to take our word for any of this. You can test it, and you can test any other provider the same way.
Test 1: Count names per ID and IDs per name
From a sample of results, build two simple tables:
- For each horse ID, how many distinct names appear?
- For each name, how many distinct horse IDs appear?
The second table is the interesting one. Names with more than one ID are your same name horses. Check that their histories are genuinely separate: different birth years, different trainers, non-overlapping careers or clearly different connections.
Test 2: Look for impossible days
Group runner rows by horse ID and race date. Any horse with two races on one day deserves a look. In a clean source the answer should be none.
Test 3: Look for duplicate runner rows
Check that each horse appears once per race. A repeated row inflates run counts and can double-count results.
Test 4: Scan for null identifiers
Count runner rows with a missing horse, jockey or trainer ID. The right number is zero.
Test 5: Re-run after an update
Take the same query before and after a daily update. Stable IDs mean earlier rows keep their keys. Your joins should not need rebuilding.
These five checks take an afternoon. They tell you more about a data source than any feature list.
What this means for each kind of reader
Model builders. Identity errors corrupt features without warning. Clean horse IDs are the foundation of any layoff, form or class feature, and of any honest backtest.
Analytics teams. Reliability in production depends on joins that never need manual repair. A gate that stops a bad update is worth more than a fix after the fact.
Tipsters and product builders. If you publish your own method, your readers see the output, not the pipeline. A wrong horse history can end up in public. Daily data that has been checked after racing protects your credibility.
What HRDB does and does not claim
We are specific about this because the claims should be easy to check.
What we do:
- Update racecards and results daily, after racing.
- Run automated integrity gates on every nightly update before it is published.
- Keep different horses that share a name separate.
- Serve stable identifiers through the API and in CSV, SQL and JSON datasets.
What we do not claim:
- This is not a live or real-time feed, and there are no webhooks. Data is updated once a day, after racing.
- Automated gates reduce the risk of identity errors. We do not describe any dataset as beyond all possibility of error, and you should still run your own checks.
- We do not offer tips or predictions. The data is the product.
Putting it into your pipeline
If you are evaluating a source, a sensible order is this:
- Pull a sample of races and runners through the API. Exchange your API key for a token with
POST /auth/token. The token is valid for 24 hours. - Run the five tests above.
- Check your remaining quota with
GET /user/usage. It reports quota used and remaining and does not consume a call. - Build your joins on IDs from the start.
- Schedule your ingestion after the daily update, not during racing.
The public API documentation lists 26 UK and 30 Hong Kong data endpoints covering races, racecards, records (results), horses, jockeys, trainers and courses, plus Hong Kong veterinary records. The horse, jockey and trainer endpoints are where you will spend most of your identity testing.
The short version
A horse’s name is not a key. Same name horses are normal in a database of 173K horses, and a merged record can hide inside a table that looks perfectly healthy.
Join on stable IDs. Test for impossible days, duplicate rows and missing IDs. Choose a source that stops a bad update before it reaches you.
Trust in racing data is rarely lost in a dramatic way. It is lost one quiet join at a time.
Next step: Explore the datasets

Comments are closed