The WorldCups.ai Database

The WorldCups.ai Database is a comprehensive database about the FIFA World Cup created by Joshua C. Fjelstul, Ph.D. It covers all 22 men's tournaments (1930–2022) and all 9 women's tournaments (1991–2023). The database includes 27 tables (over 1.58 million data points) covering all aspects of the World Cup — tournaments, teams, players, managers, referees, matches, goals, bookings, substitutions, and more.

Getting Started

The database is available in two formats.
CSV files are the easiest way to get started. Each table is a separate .csv file that you can open in Excel, Google Sheets, R, Python, or any other data tool. Many of the CSV files include pre-merged variables from related tables for convenience — for example, matches.csv already includes the team names alongside the team IDs, so you don't need to join to the teams table to see them. This makes the CSV files well-suited for casual analysis and teaching.
The relational SQL database is a PostgreSQL schema designed for users who are comfortable with SQL. In the relational version, each table contains only its own columns — merged-in convenience columns are not included. You join tables using the primary and foreign keys documented in the codebook below. This version is more compact and is the right choice for complex queries or production applications.
The relational column in each codebook table below indicates whether a variable is present in the SQL version (TRUE) or only in the CSV version (FALSE). The primary_key and foreign_key columns indicate which variables are keys.

Database Overview

The 27 tables are organized into five groups: entity tables (e.g., the basic units of observation), tournament tables, registration tables, appearance tables, and event tables.
Entity tables (9 tables): These tables contain one record per entity, which are the basic units of observation — tournaments (tournaments), confederations (confederations), teams (teams), matches (matches), players (players), managers (managers), referees (referees), stadiums (stadiums), and awards (awards). Each entity has a unique ID.
Tournament tables (6 tables): These tables describe how each tournament was organized and how each tournament progressed — which country hosted (host_countries), which teams qualified (qualified_teams), what the stages of the tournament were (tournament_stages), what the groups were (groups), what the final group standings were (group_standings), what the final tournament standings were (tournament_standings), and what awards were presented (award_winners).
Registration tables (4 tables): These tables map teams, players, managers, and referees to tournaments — which teams qualified for each tournament (qualified_teams), which players were registered in each squad (squads), which managers were appointed to which teams (manager_appointments), and which referees were appointed to each tournament (referee_appointments).
Appearance tables (4 tables): These tables map teams, players, managers, and referees to individual matches — which teams participated in each match (team_appearances), which players participated in each match (player_appearances), which managers were in charge for each match (manager_appearances), and which referees participated in each match (referee_appearances).
Event tables (4 tables): These tables record in-match events — every goal (goals), every booking (bookings), every substitution (substitutions), and every penalty kick (penalty_kicks) in a shootout. Each event has a unique ID.
GroupTableRowsDescription
Entity tablestournaments30One record per tournament
Entity tablesconfederations6One record per confederation
Entity tablesteams88One record per national team
Entity tablesmatches1,248One record per match
Entity tablesplayers10,401One record per player
Entity tablesmanagers475One record per manager
Entity tablesreferees493One record per referee
Entity tablesstadiums240One record per stadium
Entity tablesawards8One record per individual award
Tournament tableshost_countries31One record per host country per tournament
Tournament tablestournament_stages155One record per stage per tournament
Tournament tablesgroups117One record per group per stage per tournament
Tournament tablesgroup_standings626One record per team per group per stage per tournament
Tournament tablestournament_standings120One record per ranked position per tournament
Tournament tablesaward_winners200One record per award winner per tournament
Registration tablesqualified_teams625One record per team per tournament
Registration tablessquads13,843One record per player per team per tournament
Registration tablesmanager_appointments637One record per manager per team per tournament
Registration tablesreferee_appointments668One record per referee per tournament
Appearance tablesteam_appearances2,496One record per team per match
Appearance tablesplayer_appearances27,432One record per player per match (from 1970)
Appearance tablesmanager_appearances2,538One record per manager per match
Appearance tablesreferee_appearances1,248One record per referee per match
Event tablesgoals3,637One record per goal
Event tablesbookings3,178One record per booking (from 1970)
Event tablessubstitutions10,222One record per player per substitution (from 1970)
Event tablespenalty_kicks396One record per penalty kick in a shootout

License

The copyright for the original structure, organization, documentation, and replication code of the WorldCups.ai Database is owned by Joshua C. Fjelstul, Ph.D.
The database and all associated materials are published under a CC-BY-SA 4.0 license. This means you can distribute, modify, and use all or part of the database for commercial or non-commercial purposes, provided that: (1) you provide proper attribution (see below) and (2) any new works you produce based on this database also carry the CC-BY-SA 4.0 license. Contact us to discuss commercial licensing options.
Proper attribution requires: the name of the author (Joshua C. Fjelstul, Ph.D.), a copyright notice (© 2026 Joshua C. Fjelstul, Ph.D.), a link to the CC-BY-SA 4.0 license, a link to this website, and an indication of any modifications you have made.
Consistent with the CC-BY-SA 4.0 license, this database is provided as-is and as-available, with no warranties of any kind — express, implied, statutory, or otherwise — including without limitation warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors.

Common use cases

The database supports a wide range of analyses. Here are some of the most common.
Scoring and goal statistics. The goals table records every goal, including the scorer, the team, the minute, and whether the goal was an own goal or a penalty. Combined with players and teams, you can calculate all-time top scorers, goals per tournament, scoring rates by confederation or region, and how scoring patterns have changed over time.
Match results and team performance. The matches and team_appearances tables are the foundation for analyzing match outcomes. You can calculate win rates, goal differentials, home vs. away performance (the "home" team is the first-named team in the official match designation, not necessarily a team playing in their own country), and head-to-head records between any two teams.
Tournament progression. The qualified_teams, group_standings, and tournament_standings tables let you track how far teams go in each tournament. Combined with host_countries, you can analyze whether host nations perform better than expected.
Player careers. The squads and player_appearances tables track individual players across multiple tournaments. The players table includes count_tournaments and list_tournaments to make multi-tournament career analysis straightforward. Players who appeared for different countries in different tournaments (a small number of historical cases) are handled correctly — their player_id links all appearances.
Managerial records. The manager_appointments and manager_appearances tables allow you to analyze managerial win rates, which managers have appeared in the most tournaments, and patterns in which countries managers come from relative to the teams they coach.
Referee patterns. The referee_appointments and referee_appearances tables record which referees officiated which matches. Combined with bookings, you can analyze booking rates by referee, confederation, or era.
Disciplinary patterns. The bookings table records every yellow card, red card, and second yellow card since 1970. You can analyze which teams, players, or stages of tournaments see the most disciplinary action.
Substitution strategy. The substitutions table records every substitution since 1970 — who came on, who went off, and at what minute. This is useful for analyzing tactical substitution patterns and how substitution rules have changed over time.
Penalty shootouts. The penalty_kicks table records every penalty kick in every shootout, including whether it was converted. Combined with matches, you can analyze shootout outcomes, conversion rates by team or player, and which stages of tournaments most frequently go to shootouts.
Award winners. The award_winners table lets you analyze patterns in individual tournament awards — Golden Ball, Golden Boot, Golden Glove, and Best Young Player. You can look at whether award winners correlate with team success, which confederations produce the most award winners, and how often players from losing finalists win individual honors.
Host countries and stadiums. The stadiums table records the venue and city for every match. Combined with matches, you can analyze how capacity, location, and climate zone relate to match outcomes, scoring rates, or attendance patterns — and which stadiums have hosted the most matches or finals.
Tournament formats. The tournament format has changed significantly over time — from 13 teams in 1930 to 32 today, with the introduction of group stages, a round of 16, and penalty shootouts at different points. The tournaments and tournament_stages tables let you analyze how format changes have affected outcomes, match counts, and competitive balance.
Network analysis. The player_appearances and squads tables support graph-based analysis — which players shared the pitch in the same matches or squads, teammate networks across tournaments, or how national squad composition has changed over time.
Competition analysis. The database covers the men's tournaments and the women's tournaments using a consistent schema, so you can make direct comparisons across competitions — scoring rates, substitution patterns, disciplinary rates, tournament format changes over time, and how the women's tournament has grown in team count and global representation.
Teaching data science. The database is intentionally designed to support teaching. It has multiple units of analysis (tournaments, teams, players, matches, events), many opportunities for merging and reshaping data, a mix of wide and long formats, and clear primary and foreign keys. It is suitable for teaching data wrangling, data visualization, relational databases, and introductory statistics.

Key Join Patterns

The tables below document the most common multi-table join paths. In the relational database, convenience columns (team names, player names, etc.) are not included in every table — you need to join to the relevant reference table to attach them. These patterns cover the most frequent cases.
Attaching a player name to an event. Goals, bookings, and substitutions record player_id but not the player's name. Join to players on player_id to attach family_name and given_name.
SELECT g.*, p.family_name, p.given_name
FROM worldcupsai.goals g
JOIN worldcupsai.players p ON g.player_id = p.player_id
Attaching a team name to an event. Goals, bookings, and substitutions record team_id but not the team name. Join to teams on team_id to attach team_name.
SELECT g.*, t.team_name
FROM worldcupsai.goals g
JOIN worldcupsai.teams t ON g.team_id = t.team_id
Attaching a confederation to a team. teams records confederation_id but not the confederation name or code. Join to confederations on confederation_id.
SELECT t.*, c.confederation_name, c.confederation_code
FROM worldcupsai.teams t
JOIN worldcupsai.confederations c ON t.confederation_id = c.confederation_id
Attaching match context to an event. Goals, bookings, and substitutions record match_id but not the stage, date, or stadium. Join to matches on match_id to attach match-level context.
SELECT g.*, m.match_date, m.stage_name, m.group_name
FROM worldcupsai.goals g
JOIN worldcupsai.matches m ON g.match_id = m.match_id
Attaching tournament context to a match. matches records tournament_id but not the tournament name or year. Join to tournaments on tournament_id.
SELECT m.*, t.tournament_name, t.year, t.host_country
FROM worldcupsai.matches m
JOIN worldcupsai.tournaments t ON m.tournament_id = t.tournament_id
Going from a goal to a tournament. To connect an event all the way up to the tournament level, chain through matches.
SELECT g.*, t.tournament_name, t.year
FROM worldcupsai.goals g
JOIN worldcupsai.matches m ON g.match_id = m.match_id
JOIN worldcupsai.tournaments t ON m.tournament_id = t.tournament_id
Going from a player to a confederation. There is no direct link from players to confederations. Go through squads and teams.
SELECT p.family_name, p.given_name, c.confederation_code
FROM worldcupsai.players p
JOIN worldcupsai.squads s ON p.player_id = s.player_id
JOIN worldcupsai.teams t ON s.team_id = t.team_id
JOIN worldcupsai.confederations c ON t.confederation_id = c.confederation_id
Note that this join can produce multiple rows per player if a player represented teams from different confederations in different tournaments — a rare but real historical case.
Going from a match to a stadium and city. matches records stadium_id. Join to stadiums to attach the stadium name, city, and country.
SELECT m.match_id, m.match_date, s.stadium_name, s.city_name, s.country_name
FROM worldcupsai.matches m
JOIN worldcupsai.stadiums s ON m.stadium_id = s.stadium_id
Getting group standings with team names. group_standings records team_id but not the team name. Join to teams and tournaments to attach names and year.
SELECT tr.year, gs.stage_name, gs.group_name, gs.position, t.team_name, gs.points, gs.goal_difference
FROM worldcupsai.group_standings gs
JOIN worldcupsai.teams t ON gs.team_id = t.team_id
JOIN worldcupsai.tournaments tr ON gs.tournament_id = tr.tournament_id
ORDER BY tr.year, gs.group_name, gs.position

Common Queries

Here are some common queries in SQL, R, and Python that illustrate how to work with the database. Each example below is shown in SQL (using the relational database), R (using the CSV files), and Python (using the CSV files).

Top scorers

This query identifies the players with the most career World Cup goals across all tournaments. It joins the goals table with players and teams to attach names and nationalities, filters out own goals, and aggregates by player. The final unit of observation is one row per player, with a total goal count.

SQL

SELECT
  p.family_name,
  p.given_name,
  t.team_name,
  COUNT(*) AS goals
FROM worldcupsai.goals g
JOIN worldcupsai.players p ON g.player_id = p.player_id
JOIN worldcupsai.teams t ON g.player_team_id = t.team_id
WHERE g.own_goal = FALSE
GROUP BY p.player_id, p.family_name, p.given_name, t.team_name
ORDER BY goals DESC
LIMIT 10;

R

library(tidyverse)
goals <- read_csv("goals.csv")

goals |>
  filter(!own_goal) |>
  count(player_id, family_name, given_name, player_team_name, sort = TRUE) |>
  rename(goals = n) |>
  slice_head(n = 10)

Python

import pandas as pd

goals = pd.read_csv("goals.csv")

(goals
  .query("own_goal == False")
  .groupby(["player_id", "family_name", "given_name", "player_team_name"], dropna=False)
  .size()
  .reset_index(name="goals")
  .sort_values("goals", ascending=False)
  .head(10))

Win rates

This query calculates each team's overall win rate across all World Cup appearances. It uses team_appearances to get one row per team per match, joins with teams for the team name, and aggregates wins and total matches by team. The final unit of observation is one row per team, with match counts and a win percentage.

SQL

SELECT
  t.team_name,
  COUNT(*) AS matches_played,
  SUM(CASE WHEN ta.result = 'win' THEN 1 ELSE 0 END) AS wins,
  ROUND(AVG(CASE WHEN ta.result = 'win' THEN 1.0 ELSE 0 END) * 100, 1) AS win_pct
FROM worldcupsai.team_appearances ta
JOIN worldcupsai.teams t ON ta.team_id = t.team_id
GROUP BY t.team_id, t.team_name
HAVING COUNT(*) >= 10
ORDER BY win_pct DESC;

R

library(tidyverse)
team_appearances <- read_csv("team_appearances.csv")

team_appearances |>
  group_by(team_name) |>
  summarise(
    matches_played = n(),
    wins = sum(result == "win"),
    win_pct = round(wins / matches_played * 100, 1)
  ) |>
  filter(matches_played >= 10) |>
  arrange(desc(win_pct))

Python

import pandas as pd

team_appearances = pd.read_csv("team_appearances.csv")

(team_appearances
  .groupby("team_name")
  .agg(matches_played=("result", "count"), wins=("win", "sum"))
  .query("matches_played >= 10")
  .assign(win_pct=lambda x: (x["wins"] / x["matches_played"] * 100).round(1))
  .sort_values("win_pct", ascending=False))

Goals per match

This query shows how the average number of goals per match has changed across tournaments over time. It joins tournaments and matches to get the match count per tournament, then left joins goals to count goals, using a left join to correctly handle matches with no goals. The final unit of observation is one row per tournament, with total goals, total matches, and a goals-per-match ratio.

SQL

SELECT
  t.tournament_name,
  t.year,
  COUNT(g.goal_id) AS total_goals,
  COUNT(DISTINCT m.match_id) AS total_matches,
  ROUND(COUNT(g.goal_id)::NUMERIC / COUNT(DISTINCT m.match_id), 2) AS goals_per_match
FROM worldcupsai.tournaments t
JOIN worldcupsai.matches m ON t.tournament_id = m.tournament_id
LEFT JOIN worldcupsai.goals g ON m.match_id = g.match_id
GROUP BY t.tournament_id, t.tournament_name, t.year
ORDER BY t.year;

R

library(tidyverse)
tournaments <- read_csv("tournaments.csv")
matches <- read_csv("matches.csv")
goals <- read_csv("goals.csv")

matches |>
  left_join(goals |> count(match_id, name = "goals"), by = "match_id") |>
  mutate(goals = replace_na(goals, 0)) |>
  group_by(tournament_id) |>
  summarise(
    total_goals = sum(goals),
    total_matches = n(),
    goals_per_match = round(total_goals / total_matches, 2)
  ) |>
  left_join(tournaments |> select(tournament_id, tournament_name, year), by = "tournament_id") |>
  arrange(year)

Python

import pandas as pd

tournaments = pd.read_csv("tournaments.csv")
matches = pd.read_csv("matches.csv")
goals = pd.read_csv("goals.csv")

goals_per_match = goals.groupby("match_id").size().reset_index(name="goals")
matches = matches.merge(goals_per_match, on="match_id", how="left").fillna({"goals": 0})

(matches
  .groupby("tournament_id")
  .agg(total_goals=("goals", "sum"), total_matches=("match_id", "count"))
  .assign(goals_per_match=lambda x: (x["total_goals"] / x["total_matches"]).round(2))
  .reset_index()
  .merge(tournaments[["tournament_id", "tournament_name", "year"]], on="tournament_id")
  .sort_values("year"))

Penalty conversion rates

This query calculates each team's penalty shootout conversion rate using the penalty_kicks table, which records every individual kick in every shootout. It joins with teams for team names and filters to teams with at least 5 kicks to exclude small samples. The final unit of observation is one row per team, with kick counts and a conversion percentage.

SQL

SELECT
  t.team_name,
  COUNT(*) AS kicks_taken,
  SUM(CASE WHEN pk.converted THEN 1 ELSE 0 END) AS converted,
  ROUND(AVG(CASE WHEN pk.converted THEN 1.0 ELSE 0 END) * 100, 1) AS conversion_pct
FROM worldcupsai.penalty_kicks pk
JOIN worldcupsai.teams t ON pk.team_id = t.team_id
GROUP BY t.team_id, t.team_name
HAVING COUNT(*) >= 5
ORDER BY conversion_pct DESC;

R

library(tidyverse)
penalty_kicks <- read_csv("penalty_kicks.csv")

penalty_kicks |>
  group_by(team_name) |>
  summarise(
    kicks_taken = n(),
    converted = sum(converted),
    conversion_pct = round(converted / kicks_taken * 100, 1)
  ) |>
  filter(kicks_taken >= 5) |>
  arrange(desc(conversion_pct))

Python

import pandas as pd

penalty_kicks = pd.read_csv("penalty_kicks.csv")

(penalty_kicks
  .groupby("team_name")
  .agg(kicks_taken=("converted", "count"), converted=("converted", "sum"))
  .query("kicks_taken >= 5")
  .assign(conversion_pct=lambda x: (x["converted"] / x["kicks_taken"] * 100).round(1))
  .sort_values("conversion_pct", ascending=False))

Disciplinary records

This query ranks the top 5 most-carded teams in each tournament using total bookings — the sum of yellow cards, red cards, and second yellow cards. It joins bookings with teams and tournaments to attach names, aggregates by team and tournament, and uses a window function to rank teams within each tournament. The final unit of observation is one row per team per tournament, limited to the top 5 teams by booking count within each edition.

SQL

WITH bookings_per_team AS (
  SELECT
    b.tournament_id,
    t.tournament_name,
    tr.year,
    tm.team_name,
    COUNT(*) AS total_bookings,
    SUM(CASE WHEN b.yellow_card THEN 1 ELSE 0 END) AS yellow_cards,
    SUM(CASE WHEN b.red_card OR b.second_yellow_card THEN 1 ELSE 0 END) AS send_offs,
    RANK() OVER (
      PARTITION BY b.tournament_id
      ORDER BY COUNT(*) DESC
    ) AS rank
  FROM worldcupsai.bookings b
  JOIN worldcupsai.teams tm ON b.team_id = tm.team_id
  JOIN worldcupsai.matches m ON b.match_id = m.match_id
  JOIN worldcupsai.tournaments t ON b.tournament_id = t.tournament_id
  JOIN worldcupsai.tournaments tr ON b.tournament_id = tr.tournament_id
  GROUP BY b.tournament_id, t.tournament_name, tr.year, tm.team_id, tm.team_name
)
SELECT tournament_name, year, team_name, total_bookings, yellow_cards, send_offs
FROM bookings_per_team
WHERE rank <= 5
ORDER BY year, rank;

R

library(tidyverse)
bookings <- read_csv("bookings.csv")
tournaments <- read_csv("tournaments.csv")

bookings |>
  group_by(tournament_id, tournament_name, team_name) |>
  summarise(
    total_bookings = n(),
    yellow_cards = sum(yellow_card),
    send_offs = sum(red_card | second_yellow_card),
    .groups = "drop"
  ) |>
  left_join(tournaments |> select(tournament_id, year), by = "tournament_id") |>
  group_by(tournament_id) |>
  slice_max(total_bookings, n = 5, with_ties = FALSE) |>
  arrange(year, desc(total_bookings))

Python

import pandas as pd

bookings = pd.read_csv("bookings.csv")
tournaments = pd.read_csv("tournaments.csv")

carded = (bookings
  .groupby(["tournament_id", "tournament_name", "team_name"])
  .agg(
    total_bookings=("booking_id", "count"),
    yellow_cards=("yellow_card", "sum"),
    send_offs=("sending_off", "sum")
  )
  .reset_index()
  .merge(tournaments[["tournament_id", "year"]], on="tournament_id"))

(carded
  .sort_values("total_bookings", ascending=False)
  .groupby("tournament_id")
  .head(5)
  .sort_values(["year", "total_bookings"], ascending=[True, False]))

Table Reference

For each table, the codebook lists every variable with its type, a description, and flags indicating whether the column is included in the relational version of the database, and whether it is a primary or foreign key.
Variable types:
  • uuid — a universally unique identifier (the primary key format used throughout the database)
  • text — a free-form text string
  • integer — a whole number
  • boolean — a boolean (coded TRUE/FALSE)
  • date — a calendar date in YYYY-MM-DD format
  • enum — a text value constrained to a fixed set of allowed values (listed in the description)

Tournaments

One record per tournament. Covers all 22 men's World Cups (1930–2022) and all 8 women's World Cups (1991–2019).
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament.
tournament_nametextThe name of the tournament.
menbooleanWhether the tournament is a men's tournament. Coded 1 if the tournament is a men's tournament and 0 otherwise.
womenbooleanWhether the tournament is a women's tournament. Coded 1 if the tournament is a women's tournament and 0 otherwise.
yearintegerThe year of the tournament.
start_datedateThe start date of the tournament in the format YYYY-MM-DD.
end_datedateThe end date of the tournament in the format YYYY-MM-DD.
host_countrytextThe name of the country that hosted the tournament.
host_wonbooleanWhether one of the host countries won the tournament. Coded 1 if one of the host countries won and 0 otherwise.
count_teamsintegerThe number of teams that participated in the tournament.
group_stagebooleanWhether the tournament had a group stage. Coded 1 if there was a group stage and 0 otherwise.
second_group_stagebooleanWhether the tournament had a second group stage. Coded 1 if there was a second group stage and 0 otherwise.
final_roundbooleanWhether the tournament had a final round. Coded 1 if there was a final round and 0 otherwise.
round_of_16booleanWhether the tournament had a round of 16. Coded 1 if there was a round of 16 and 0 otherwise.
quarter_finalsbooleanWhether the tournament had a quarter-finals stage. Coded 1 if there was a quarter-finals stage and 0 otherwise.
semi_finalsbooleanWhether the tournament had a semi-finals stage. Coded 1 if there was a semi-finals stage and 0 otherwise.
third_place_matchbooleanWhether the tournament had a third-place match. Coded 1 if there was a third-place match and 0 otherwise.
finalbooleanWhether the tournament had a final match. Coded 1 if there was a final match and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.tournaments(
  tournament_id UUID NOT NULL,
  tournament_name TEXT NOT NULL,
  men BOOLEAN NOT NULL,
  women BOOLEAN NOT NULL,
  year INTEGER NOT NULL,
  start_date DATE NOT NULL,
  end_date DATE NOT NULL,
  host_country TEXT NOT NULL,
  host_won BOOLEAN NOT NULL,
  count_teams INTEGER NOT NULL,
  group_stage BOOLEAN NOT NULL,
  second_group_stage BOOLEAN NOT NULL,
  final_round BOOLEAN NOT NULL,
  round_of_16 BOOLEAN NOT NULL,
  quarter_finals BOOLEAN NOT NULL,
  semi_finals BOOLEAN NOT NULL,
  third_place_match BOOLEAN NOT NULL,
  final BOOLEAN NOT NULL,
  PRIMARY KEY (tournament_id),
  CONSTRAINT dates_ordered CHECK (end_date >= start_date)
);

Confederations

One record per FIFA confederation. There are 6 confederations: AFC (Asia), CAF (Africa), CONCACAF (North/Central America and Caribbean), CONMEBOL (South America), OFC (Oceania), and UEFA (Europe).
VariableTypeDescriptionRelPKFK
confederation_iduuidThe unique ID number for the confederation.
confederation_nametextThe name of the confederation.
confederation_codetextThe abbreviation for the confederation.
wikipedia_link_confederationtextThe Wikipedia link for the confederation.

SQL schema

CREATE TABLE worldcupsai.confederations(
  confederation_id UUID NOT NULL,
  confederation_name TEXT NOT NULL,
  confederation_code TEXT NOT NULL,
  wikipedia_link_confederation TEXT,
  PRIMARY KEY (confederation_id)
);

Teams

One record per national team. A small number of teams appear in both the men's and women's tournaments; these are recorded as a single row with both men and women set to TRUE.
VariableTypeDescriptionRelPKFK
team_iduuidThe unique ID number for the team.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
menbooleanWhether the team is a men's team. Coded 1 if the team is a men's team and 0 otherwise.
womenbooleanWhether the team is a women's team. Coded 1 if the team is a women's team and 0 otherwise.
federation_nametextThe name of the team's federation.
region_nameenumThe name of the region that the country is located in. Possible values: Africa, Caribbean, Central America, East Asia, Europe, Europe, Asia, Middle East, North America, Oceania, South America, South East Asia.
confederation_iduuidThe unique ID number for the confederation. References confederation_id in the confederations table.
confederation_nametextThe name of the confederation.
confederation_codetextThe abbreviation for the confederation.
wikipedia_link_mentextThe Wikipedia link for the country's men's team. Null if the country's men's team has not qualified for a tournament.
wikipedia_link_womentextThe Wikipedia link for the country's women's team. Null if the country's women's team has not qualified for a tournament.
wikipedia_link_federationtextThe Wikipedia link for the team's federation.

SQL schema

CREATE TABLE worldcupsai.teams(
  team_id UUID NOT NULL,
  team_name TEXT NOT NULL,
  team_code TEXT NOT NULL,
  men BOOLEAN NOT NULL,
  women BOOLEAN NOT NULL,
  federation_name TEXT NOT NULL,
  region_name worldcupsai.region_name NOT NULL,
  confederation_id UUID NOT NULL,
  wikipedia_link_men TEXT,
  wikipedia_link_women TEXT,
  wikipedia_link_federation TEXT,
  PRIMARY KEY (team_id),
  FOREIGN KEY (confederation_id) REFERENCES worldcupsai.confederations (confederation_id)
);

Matches

One record per match. The central table of the database — most analyses start here or join through this table. The home_team is the first-named team in the official match designation; in most cases this is not meaningful (it does not indicate which country the match was played in), but it is consistent with the official FIFA match records.
A small number of matches were replayed due to exceptional circumstances. The original match has replayed = TRUE and the replacement match has replay = TRUE. Both records are included.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match.
match_nametextThe name of the match.
stage_nameenumThe stage of the tournament in which the match occurred. Possible values: group stage, first group stage, second group stage, first round, final round, round of 16, quarter-finals, semi-finals, third-place match, final.
group_nameenumThe name of the group. Null for knockout stage matches. Possible values: Group 1, Group 2, Group 3, Group 4, Group 5, Group 6, Group A, Group B, Group C, Group D, Group E, Group F, Group G, Group H.
group_stagebooleanWhether the match is a group stage match. Coded 1 if the match is a group stage match and 0 otherwise.
knockout_stagebooleanWhether the match is a knockout stage match. Coded 1 if the match is a knockout stage match and 0 otherwise.
replayedbooleanWhether the match was later replayed. Coded 1 if the match was replayed and 0 otherwise.
replaybooleanWhether the match is itself a replay of an earlier match. Coded 1 if the match is a replay and 0 otherwise.
match_datedateThe date of the match in the format YYYY-MM-DD.
match_timetextThe time of the match in the format HH:MM.
stadium_iduuidThe unique ID number for the stadium. References stadium_id in the stadiums table.
stadium_nametextThe name of the stadium.
city_nametextThe name of the city in which the match was played.
country_nametextThe name of the country in which the match was played.
home_team_iduuidThe unique ID number for the home team. References team_id in the teams table.
home_team_nametextThe name of the home team.
home_team_codetextThe 3-letter code for the home team.
away_team_iduuidThe unique ID number for the away team. References team_id in the teams table.
away_team_nametextThe name of the away team.
away_team_codetextThe 3-letter code for the away team.
scoretextThe score of the match in the format #-#, where the first number is the score of the home team and the second number is the score of the away team.
home_team_scoreintegerThe number of goals scored by the home team.
away_team_scoreintegerThe number of goals scored by the away team.
home_team_score_marginintegerThe goal margin for the home team. Positive if the home team won and negative if the away team won.
away_team_score_marginintegerThe goal margin for the away team. Positive if the away team won and negative if the home team won.
extra_timebooleanWhether the match went to extra time. Coded 1 if the match went to extra time and 0 otherwise.
penalty_shootoutbooleanWhether the match ended in a penalty shootout. Coded 1 if the match ended in a penalty shootout and 0 otherwise.
score_penaltiestextThe score of the penalty shootout in the format #-#. Null if there was not a penalty shootout.
home_team_score_penaltiesintegerThe number of penalties scored by the home team in the shootout. Null if there was not a penalty shootout.
away_team_score_penaltiesintegerThe number of penalties scored by the away team in the shootout. Null if there was not a penalty shootout.
resultenumThe result of the match. Possible values: home team win, away team win, draw. Derived from home_team_win, away_team_win, and draw.
home_team_winbooleanWhether the home team won the match. Coded 1 if the home team won and 0 otherwise.
away_team_winbooleanWhether the away team won the match. Coded 1 if the away team won and 0 otherwise.
drawbooleanWhether the match ended in a draw. Coded 1 if the match ended in a draw and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.matches(
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  match_name TEXT NOT NULL,
  stage_name worldcupsai.stage_name NOT NULL,
  group_name worldcupsai.group_name,
  group_stage BOOLEAN NOT NULL,
  knockout_stage BOOLEAN NOT NULL,
  replayed BOOLEAN NOT NULL,
  replay BOOLEAN NOT NULL,
  match_date DATE NOT NULL,
  match_time TEXT NOT NULL,
  stadium_id UUID NOT NULL,
  home_team_id UUID NOT NULL,
  away_team_id UUID NOT NULL,
  score TEXT NOT NULL,
  home_team_score INTEGER NOT NULL,
  away_team_score INTEGER NOT NULL,
  home_team_score_margin INTEGER NOT NULL,
  away_team_score_margin INTEGER NOT NULL,
  extra_time BOOLEAN NOT NULL,
  penalty_shootout BOOLEAN NOT NULL,
  score_penalties TEXT,
  home_team_score_penalties INTEGER,
  away_team_score_penalties INTEGER,
  result worldcupsai.match_result NOT NULL,
  home_team_win BOOLEAN NOT NULL,
  away_team_win BOOLEAN NOT NULL,
  draw BOOLEAN NOT NULL,
  PRIMARY KEY (match_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (stadium_id) REFERENCES worldcupsai.stadiums (stadium_id),
  FOREIGN KEY (home_team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (away_team_id) REFERENCES worldcupsai.teams (team_id),
  CONSTRAINT different_teams CHECK (home_team_id != away_team_id),
  CONSTRAINT penalties_iff_shootout CHECK (
    (penalty_shootout = TRUE AND home_team_score_penalties IS NOT NULL
      AND away_team_score_penalties IS NOT NULL)
    OR
    (penalty_shootout = AND home_team_score_penalties IS NULL
      AND away_team_score_penalties IS NULL)
  ),
  CONSTRAINT margins_consistent CHECK (
    home_team_score_margin = home_team_score - away_team_score AND
    away_team_score_margin = away_team_score - home_team_score
  ),
  CONSTRAINT result_flags_consistent CHECK (
    (home_team_win = TRUE AND away_team_win = AND draw = FALSE
      AND result = 'home team win') OR
    (home_team_win = AND away_team_win = TRUE AND draw = FALSE
      AND result = 'away team win') OR
    (home_team_win = AND away_team_win = AND draw = TRUE
      AND result = 'draw')
  ),
  CONSTRAINT shootout_requires_extra_time CHECK (
    penalty_shootout = OR extra_time = TRUE
  ),
  CONSTRAINT scores_non_negative CHECK (
    home_team_score >= 0 AND away_team_score >= 0
  ),
  CONSTRAINT group_name_iff_group_stage CHECK (
    (group_stage = TRUE AND group_name IS NOT NULL) OR
    (knockout_stage = TRUE AND group_name IS NULL)
  )
);

Players

One record per player. Covers all players who appeared in a squad, including players on the bench who did not enter the match. A small number of players represented different countries in different tournaments; these players appear only once in this table, with their player_id linking all of their appearances.
VariableTypeDescriptionRelPKFK
player_iduuidThe unique ID number for the player.
family_nametextThe family name of the player. For players known by a single name (e.g., Pelé), this field records that name and given_name is null.
given_nametextThe given name of the player. Null if not available or not applicable.
birth_datedateThe birth date of the player in the format YYYY-MM-DD. Null if not available.
malebooleanWhether the player is male. Coded 1 if the player is male and 0 otherwise.
femalebooleanWhether the player is female. Coded 1 if the player is female and 0 otherwise.
goal_keeperbooleanWhether the player has played as a goal keeper. Coded 1 if the player has played as a goal keeper and 0 otherwise.
defenderbooleanWhether the player has played as a defender. Coded 1 if the player has played as a defender and 0 otherwise.
midfielderbooleanWhether the player has played as a midfielder. Coded 1 if the player has played as a midfielder and 0 otherwise.
forwardbooleanWhether the player has played as a forward. Coded 1 if the player has played as a forward and 0 otherwise.
count_tournamentsintegerThe number of tournaments the player participated in. Derived from the squads table.
list_tournamentstextA comma-separated list of tournament years the player participated in. Derived from the squads table.
wikipedia_link_playertextThe Wikipedia link for the player. Null if not available.

SQL schema

CREATE TABLE worldcupsai.players(
  player_id UUID NOT NULL,
  family_name TEXT NOT NULL,
  given_name TEXT,
  birth_date DATE,
  male BOOLEAN NOT NULL,
  female BOOLEAN NOT NULL,
  goal_keeper BOOLEAN NOT NULL,
  defender BOOLEAN NOT NULL,
  midfielder BOOLEAN NOT NULL,
  forward BOOLEAN NOT NULL,
  count_tournaments INTEGER NOT NULL,
  list_tournaments TEXT NOT NULL,
  wikipedia_link_player TEXT,
  PRIMARY KEY (player_id)
);

Managers

One record per manager.
VariableTypeDescriptionRelPKFK
manager_iduuidThe unique ID number for the manager.
family_nametextThe family name of the manager.
given_nametextThe given name of the manager. Null if not available.
malebooleanWhether the manager is male. Coded 1 if the manager is male and 0 otherwise.
femalebooleanWhether the manager is female. Coded 1 if the manager is female and 0 otherwise.
country_nametextThe name of the manager's home country.
wikipedia_link_managertextThe Wikipedia link for the manager. Null if not available.

SQL schema

CREATE TABLE worldcupsai.managers(
  manager_id UUID NOT NULL,
  family_name TEXT NOT NULL,
  given_name TEXT,
  male BOOLEAN NOT NULL,
  female BOOLEAN NOT NULL,
  country_name TEXT NOT NULL,
  wikipedia_link_manager TEXT,
  PRIMARY KEY (manager_id)
);

Referees

One record per referee. Only the main match referee is recorded; assistant referees, fourth officials, and video assistant referees are not included.
VariableTypeDescriptionRelPKFK
referee_iduuidThe unique ID number for the referee.
family_nametextThe family name of the referee.
given_nametextThe given name of the referee. Null if not available.
malebooleanWhether the referee is male. Coded 1 if the referee is male and 0 otherwise.
femalebooleanWhether the referee is female. Coded 1 if the referee is female and 0 otherwise.
country_nametextThe name of the referee's home country.
confederation_iduuidThe unique ID number for the confederation. References confederation_id in the confederations table.
confederation_nametextThe name of the confederation.
confederation_codetextThe abbreviation for the confederation.
wikipedia_link_refereetextThe Wikipedia link for the referee. Null if not available.

SQL schema

CREATE TABLE worldcupsai.referees(
  referee_id UUID NOT NULL,
  family_name TEXT NOT NULL,
  given_name TEXT,
  male BOOLEAN NOT NULL,
  female BOOLEAN NOT NULL,
  country_name TEXT NOT NULL,
  confederation_id UUID NOT NULL,
  wikipedia_link_referee TEXT,
  PRIMARY KEY (referee_id),
  FOREIGN KEY (confederation_id) REFERENCES worldcupsai.confederations (confederation_id)
);

Stadiums

One record per stadium. Stadium capacity is the approximate capacity at the time the stadium hosted World Cup matches; it may differ from the current capacity.
VariableTypeDescriptionRelPKFK
stadium_iduuidThe unique ID number for the stadium.
stadium_nametextThe name of the stadium.
city_nametextThe name of the city in which the stadium is located.
country_nametextThe name of the country in which the stadium is located.
stadium_capacityintegerThe approximate capacity of the stadium.
wikipedia_link_stadiumtextThe Wikipedia link for the stadium. Null if not available.
wikipedia_link_citytextThe Wikipedia link for the city. Null if not available.

SQL schema

CREATE TABLE worldcupsai.stadiums(
  stadium_id UUID NOT NULL,
  stadium_name TEXT NOT NULL,
  city_name TEXT NOT NULL,
  country_name TEXT NOT NULL,
  stadium_capacity INTEGER NOT NULL,
  wikipedia_link_stadium TEXT,
  wikipedia_link_city TEXT,
  PRIMARY KEY (stadium_id)
);

Awards

One record per individual award. Awards are handed out to players at each tournament.
VariableTypeDescriptionRelPKFK
award_iduuidThe unique ID number for the award.
award_nameenumThe name of the award. Possible values: Golden Ball, Silver Ball, Bronze Ball, Golden Boot, Silver Boot, Bronze Boot, Golden Glove, Best Young Player.
award_descriptiontextA description of the award.
year_introducedintegerThe year the award was first introduced.

SQL schema

CREATE TABLE worldcupsai.awards(
  award_id UUID NOT NULL,
  award_name TEXT NOT NULL,
  award_description TEXT NOT NULL,
  year_introduced INTEGER NOT NULL,
  PRIMARY KEY (award_id)
);

Host countries

One record per host country per tournament. Some tournaments had multiple host countries (e.g., the 2002 tournament was co-hosted by South Korea and Japan).
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
team_iduuidThe unique ID number for the host country's team. References team_id in the teams table.
team_nametextThe name of the host country's team.
team_codetextThe 3-letter code for the host country's team.
progressenumThe furthest stage of the tournament reached by the host country's team. Possible values: group stage, first group stage, second group stage, first round, final round, round of 16, quarter-finals, semi-finals, third-place match, final.

SQL schema

CREATE TABLE worldcupsai.host_countries(
  tournament_id UUID NOT NULL,
  team_id UUID NOT NULL,
  progress worldcupsai.stage_name NOT NULL,
  PRIMARY KEY (tournament_id, team_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id)
);

Tournament stages

One record per stage per tournament. A stage is a distinct phase of the tournament, such as the group stage or the quarter-finals. The 1974, 1978, and 1982 tournaments had two group stages; these each appear as separate rows. The stage_number field gives the chronological order of stages within a tournament.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
stage_numberintegerThe sequential number of the stage within the tournament.
stage_nameenumThe name of the stage. Possible values: group stage, first group stage, second group stage, first round, final round, round of 16, quarter-finals, semi-finals, third-place match, final.
group_stagebooleanWhether the stage is a group stage. Coded 1 if the stage is a group stage and 0 otherwise.
knockout_stagebooleanWhether the stage is a knockout stage. Coded 1 if the stage is a knockout stage and 0 otherwise.
unbalanced_groupsbooleanWhether the groups in the stage are unbalanced. Coded 1 if the groups are unbalanced and 0 otherwise. Null for knockout stages.
start_datedateThe start date of the stage in the format YYYY-MM-DD.
end_datedateThe end date of the stage in the format YYYY-MM-DD.
count_matchesintegerThe number of matches in the stage.
count_teamsintegerThe number of teams that participated in the stage.
count_scheduledintegerThe number of matches that were scheduled in the stage.
count_replaysintegerThe number of replayed matches in the stage.
count_playoffsintegerThe number of playoff matches in the stage.
count_walkoversintegerThe number of walkovers in the stage.

SQL schema

CREATE TABLE worldcupsai.tournament_stages(
  tournament_id UUID NOT NULL,
  stage_number INTEGER NOT NULL,
  stage_name worldcupsai.stage_name NOT NULL,
  group_stage BOOLEAN NOT NULL,
  knockout_stage BOOLEAN NOT NULL,
  unbalanced_groups BOOLEAN,
  start_date DATE NOT NULL,
  end_date DATE NOT NULL,
  count_matches INTEGER NOT NULL,
  count_teams INTEGER NOT NULL,
  count_scheduled INTEGER NOT NULL,
  count_replays INTEGER NOT NULL,
  count_playoffs INTEGER NOT NULL,
  count_walkovers INTEGER NOT NULL,
  PRIMARY KEY (tournament_id, stage_number),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  CONSTRAINT dates_ordered CHECK (end_date >= start_date),
  CONSTRAINT unbalanced_groups_only_in_group_stage CHECK (
    group_stage = TRUE OR unbalanced_groups IS NOT DISTINCT FROM FALSE
  )
);

Groups

One record per group per stage per tournament. The 1934 and 1938 tournaments did not have a group stage, so they have no rows in this table. Through 1970, group names used numbers (Group 1, Group 2, etc.). Since 1982, group names have used letters (Group A, Group B, etc.). The 1974, 1978, and 1982 tournaments had two group stages; groups from both stages are included.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
stage_numberintegerThe sequential number of the stage within the tournament. References stage_number in the tournament_stages table.
stage_nameenumThe name of the stage. Possible values: group stage, first group stage, second group stage, first round, final round, round of 16, quarter-finals, semi-finals, third-place match, final.
group_nameenumThe name of the group. Possible values: Group 1, Group 2, Group 3, Group 4, Group 5, Group 6, Group A, Group B, Group C, Group D, Group E, Group F, Group G, Group H.
count_teamsintegerThe number of teams in the group.

SQL schema

CREATE TABLE worldcupsai.groups(
  tournament_id UUID NOT NULL,
  stage_number INTEGER NOT NULL,
  stage_name worldcupsai.stage_name NOT NULL,
  group_name worldcupsai.group_name NOT NULL,
  count_teams INTEGER NOT NULL,
  PRIMARY KEY (tournament_id, stage_number, group_name),
  FOREIGN KEY (tournament_id, stage_number)
    REFERENCES worldcupsai.tournament_stages (tournament_id, stage_number),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id)
);

Group standings

One record per team per group per stage per tournament. Final positions account for tie-breakers as applied in the original tournament. Points follow the 3-1-0 system (3 points for a win, 1 for a draw, 0 for a loss) for tournaments from 1994 onward, and the 2-1-0 system for earlier tournaments.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
stage_numberintegerThe sequential number of the stage within the tournament. References stage_number in the tournament_stages table.
stage_nameenumThe name of the stage. Possible values: group stage, first group stage, second group stage, first round, final round, round of 16, quarter-finals, semi-finals, third-place match, final.
group_nameenumThe name of the group. Possible values: Group 1, Group 2, Group 3, Group 4, Group 5, Group 6, Group A, Group B, Group C, Group D, Group E, Group F, Group G, Group H.
positionintegerThe team's final position in the group.
team_iduuidThe unique ID number for the team. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
playedintegerThe number of matches that the team played in the group.
winsintegerThe number of matches that the team won in the group.
drawsintegerThe number of matches that the team drew in the group.
lossesintegerThe number of matches that the team lost in the group.
goals_forintegerThe number of goals scored by the team in the group.
goals_againstintegerThe number of goals scored against the team in the group.
goal_differenceintegerThe team's goal difference in the group (goals_for minus goals_against).
pointsintegerThe number of points earned by the team in the group.
advancedbooleanWhether the team advanced out of the group. Coded 1 if the team advanced and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.group_standings(
  tournament_id UUID NOT NULL,
  stage_number INTEGER NOT NULL,
  stage_name worldcupsai.stage_name NOT NULL,
  group_name worldcupsai.group_name NOT NULL,
  position INTEGER NOT NULL,
  team_id UUID NOT NULL,
  played INTEGER NOT NULL,
  wins INTEGER NOT NULL,
  draws INTEGER NOT NULL,
  losses INTEGER NOT NULL,
  goals_for INTEGER NOT NULL,
  goals_against INTEGER NOT NULL,
  goal_difference INTEGER NOT NULL,
  points INTEGER NOT NULL,
  advanced BOOLEAN NOT NULL,
  PRIMARY KEY (tournament_id, stage_number, group_name, position),
  FOREIGN KEY (tournament_id, stage_number)
    REFERENCES worldcupsai.tournament_stages (tournament_id, stage_number),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  CONSTRAINT unique_team_in_group UNIQUE (tournament_id, stage_number, group_name, team_id),
  CONSTRAINT standings_non_negative CHECK (
    played >= 0 AND wins >= 0 AND draws >= 0 AND losses >= 0 AND
    goals_for >= 0 AND goals_against >= 0 AND points >= 0
  ),
  CONSTRAINT played_consistent CHECK (played = wins + draws + losses),
  CONSTRAINT goal_diff_consistent CHECK (goal_difference = goals_for - goals_against),
  CONSTRAINT points_consistent CHECK (
    points = (wins * 3) + draws OR
    points = (wins * 2) + draws
  )
);

Tournament standings

One record per ranked position per tournament. Only the top four teams are ranked. In most tournaments these are: 1st (winner of the final), 2nd (loser of the final), 3rd (winner of the third-place match), and 4th (loser of the third-place match). In tournaments without a third-place match, only positions 1 and 2 are recorded.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
positionintegerThe final position of the team in the tournament.
team_iduuidThe unique ID number for the team. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.

SQL schema

CREATE TABLE worldcupsai.tournament_standings(
  tournament_id UUID NOT NULL,
  position INTEGER NOT NULL,
  team_id UUID NOT NULL,
  PRIMARY KEY (tournament_id, position),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  CONSTRAINT unique_team_in_standings UNIQUE (tournament_id, team_id)
);

Award winners

One record per player per award per tournament. Some awards are shared; these appear as multiple rows with the same tournament_id and award_id and shared = TRUE.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
award_iduuidThe unique ID number for the award. References award_id in the awards table.
award_nameenumThe name of the award. Possible values: Golden Ball, Silver Ball, Bronze Ball, Golden Boot, Silver Boot, Bronze Boot, Golden Glove, Best Young Player.
sharedbooleanWhether the award was shared between multiple players. Coded 1 if the award was shared and 0 otherwise.
player_iduuidThe unique ID number for the player who won the award. References player_id in the players table.
family_nametextThe family name of the player who won the award.
given_nametextThe given name of the player who won the award.
team_iduuidThe unique ID number for the team of the player who won the award. References team_id in the teams table.
team_nametextThe name of the team of the player who won the award.
team_codetextThe 3-letter code for the team of the player who won the award.

SQL schema

CREATE TABLE worldcupsai.award_winners(
  tournament_id UUID NOT NULL,
  award_id UUID NOT NULL,
  shared BOOLEAN NOT NULL,
  player_id UUID NOT NULL,
  team_id UUID NOT NULL,
  PRIMARY KEY (tournament_id, award_id, player_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (award_id) REFERENCES worldcupsai.awards (award_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id)
);

Qualified teams

One record per team per tournament. The progress variable records the furthest stage of the tournament that each team reached. Note that progress is derived from the last match each team played in — it reflects the stage of the tournament in which the team played their final match, which is the furthest stage they reached.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
team_iduuidThe unique ID number for the team. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
count_matchesintegerThe number of matches that the team played in the tournament.
progressenumThe furthest stage of the tournament reached by the team. Possible values: group stage, first group stage, second group stage, first round, final round, round of 16, quarter-finals, semi-finals, third-place match, final.

SQL schema

CREATE TABLE worldcupsai.qualified_teams(
  tournament_id UUID NOT NULL,
  team_id UUID NOT NULL,
  count_matches INTEGER NOT NULL,
  progress worldcupsai.stage_name NOT NULL,
  PRIMARY KEY (tournament_id, team_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id)
);

Squads

One record per player per team per tournament. Covers all registered squad members, including players who did not enter any match. Shirt numbers are only available from 1954; they are coded 0 for tournaments prior to 1954.
Note that squads uses coarse squad-registration positions (goal keeper, defender, midfielder, forward), while player_appearances uses granular match-day positions (21 possible values). These are different position vocabularies serving different purposes.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
team_iduuidThe unique ID number for the team. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
player_iduuidThe unique ID number for the player. References player_id in the players table.
family_nametextThe family name of the player.
given_nametextThe given name of the player.
shirt_numberintegerThe shirt number of the player. Coded 0 for tournaments prior to 1954.
squad_position_nameenumThe position of the player as registered in the squad. Possible values: goal keeper, defender, midfielder, forward.
squad_position_codeenumThe code for the position of the player as registered in the squad. Possible values: GK, DF, MF, FW.

SQL schema

CREATE TABLE worldcupsai.squads(
  tournament_id UUID NOT NULL,
  team_id UUID NOT NULL,
  player_id UUID NOT NULL,
  shirt_number INTEGER NOT NULL,
  squad_position_name worldcupsai.squad_position_name NOT NULL,
  squad_position_code worldcupsai.squad_position_code NOT NULL,
  PRIMARY KEY (tournament_id, team_id, player_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  CONSTRAINT shirt_number_valid CHECK (shirt_number BETWEEN 0 AND 99)
);

Manager appointments

One record per manager per team per tournament. Some teams have co-managers; these appear as separate rows for the same team and tournament.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
team_iduuidThe unique ID number for the team. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
manager_iduuidThe unique ID number for the manager. References manager_id in the managers table.
family_nametextThe family name of the manager.
given_nametextThe given name of the manager.
country_nametextThe name of the manager's home country.

SQL schema

CREATE TABLE worldcupsai.manager_appointments(
  tournament_id UUID NOT NULL,
  team_id UUID NOT NULL,
  manager_id UUID NOT NULL,
  PRIMARY KEY (tournament_id, team_id, manager_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (manager_id) REFERENCES worldcupsai.managers (manager_id)
);

Referee appointments

One record per referee per tournament. Only the main match referee is recorded; assistant referees, fourth officials, and video assistant referees are not included.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
referee_iduuidThe unique ID number for the referee. References referee_id in the referees table.
family_nametextThe family name of the referee.
given_nametextThe given name of the referee.
country_nametextThe name of the referee's home country.
confederation_iduuidThe unique ID number for the confederation.
confederation_nametextThe name of the confederation.
confederation_codetextThe abbreviation for the confederation.

SQL schema

CREATE TABLE worldcupsai.referee_appointments(
  tournament_id UUID NOT NULL,
  referee_id UUID NOT NULL,
  PRIMARY KEY (tournament_id, referee_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (referee_id) REFERENCES worldcupsai.referees (referee_id)
);

Team appearances

One record per team per match. There are always exactly two rows per match — one for the home team and one for the away team. This table is particularly useful for per-team analysis across many matches, because it puts each team's perspective in its own row rather than a single row with home and away columns.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
stage_nameenumThe stage of the tournament in which the match occurred. Possible values as for matches.
group_nameenumThe name of the group.
group_stagebooleanWhether the match is a group stage match.
knockout_stagebooleanWhether the match is a knockout stage match.
replayedbooleanWhether the match was later replayed.
replaybooleanWhether the match is itself a replay.
match_datedateThe date of the match.
match_timetextThe time of the match.
stadium_iduuidThe unique ID number for the stadium.
stadium_nametextThe name of the stadium.
city_nametextThe name of the city.
country_nametextThe name of the country.
team_iduuidThe unique ID number for the team. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
opponent_iduuidThe unique ID number for the opposing team. References team_id in the teams table.
opponent_nametextThe name of the opposing team.
opponent_codetextThe 3-letter code for the opposing team.
home_teambooleanWhether the team was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team was the away team. Coded 1 if the team was the away team and 0 otherwise.
goals_forintegerThe number of goals scored by the team.
goals_againstintegerThe number of goals scored against the team.
goal_differentialintegerThe team's goal differential (goals_for minus goals_against).
extra_timebooleanWhether the match went to extra time. Coded 1 if the match went to extra time and 0 otherwise.
penalty_shootoutbooleanWhether the match ended in a penalty shootout. Coded 1 if the match ended in a penalty shootout and 0 otherwise.
penalties_forintegerThe number of penalties scored by the team in the shootout. Null if there was not a penalty shootout.
penalties_againstintegerThe number of penalties scored against the team in the shootout. Null if there was not a penalty shootout.
resultenumThe result of the match from the perspective of the team. Possible values: win, lose, draw.
winbooleanWhether the team won the match. Coded 1 if the team won and 0 otherwise.
losebooleanWhether the team lost the match. Coded 1 if the team lost and 0 otherwise.
drawbooleanWhether the match ended in a draw. Coded 1 if the match ended in a draw and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.team_appearances(
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  opponent_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  goals_for INTEGER NOT NULL,
  goals_against INTEGER NOT NULL,
  goal_differential INTEGER NOT NULL,
  extra_time BOOLEAN NOT NULL,
  penalty_shootout BOOLEAN NOT NULL,
  penalties_for INTEGER,
  penalties_against INTEGER,
  result worldcupsai.team_result NOT NULL,
  win BOOLEAN NOT NULL,
  lose BOOLEAN NOT NULL,
  draw BOOLEAN NOT NULL,
  PRIMARY KEY (tournament_id, match_id, team_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (opponent_id) REFERENCES worldcupsai.teams (team_id),
  CONSTRAINT result_flags_consistent CHECK (
    (win = TRUE AND lose = AND draw = AND result = 'win') OR
    (win = AND lose = TRUE AND draw = AND result = 'lose') OR
    (win = AND lose = AND draw = TRUE AND result = 'draw')
  ),
  CONSTRAINT goal_diff_consistent CHECK (
    goal_differential = goals_for - goals_against
  ),
  CONSTRAINT shootout_requires_extra_time CHECK (
    penalty_shootout = OR extra_time = TRUE
  ),
  CONSTRAINT scores_non_negative CHECK (goals_for >= 0 AND goals_against >= 0),
  CONSTRAINT penalties_non_negative CHECK (
    penalties_for >= 0 AND penalties_against >= 0
  ),
  CONSTRAINT penalties_iff_shootout CHECK (
    (penalty_shootout = TRUE AND penalties_for IS NOT NULL
      AND penalties_against IS NOT NULL) OR
    (penalty_shootout = AND penalties_for IS NULL
      AND penalties_against IS NULL)
  ),
  CONSTRAINT home_or_away CHECK (home_team != away_team)
);

Player appearances

One record per player per team per match per tournament. Coverage starts in 1970; FIFA match reports before 1970 do not record substitution information, so pre-1970 lineups are not available. Only players who actually played — either as starters or as substitutes who entered the match — are included. Players who were in the squad but remained on the bench throughout are in squads but not player_appearances.
Note that position_name and position_code here use a granular 21-value system for match-day positions, which is different from the 4-value system used in squads for squad-registration positions.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
team_iduuidThe unique ID number for the team of the player. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
home_teambooleanWhether the team was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team was the away team. Coded 1 if the team was the away team and 0 otherwise.
player_iduuidThe unique ID number for the player. References player_id in the players table.
family_nametextThe family name of the player.
given_nametextThe given name of the player.
shirt_numberintegerThe shirt number of the player.
position_nameenumThe position of the player in the match. Possible values: attacking midfielder, center back, center forward, center midfielder, defender, defensive midfielder, forward, goal keeper, left back, left forward, left midfielder, left wing back, left winger, midfielder, right back, right forward, right midfielder, right wing back, right winger, second striker, sweeper.
position_codeenumThe code for the position of the player in the match. Possible values: AM, CB, CF, CM, DF, DM, FW, GK, LB, LF, LM, LW, LWB, MF, RB, RF, RM, RW, RWB, SS, SW.
starterbooleanWhether the player started the match. Coded 1 if the player started the match and 0 otherwise.
substitutebooleanWhether the player entered the match as a substitute. Coded 1 if the player was a substitute and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.player_appearances(
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  player_id UUID NOT NULL,
  shirt_number INTEGER NOT NULL,
  position_name worldcupsai.player_position_name NOT NULL,
  position_code worldcupsai.player_position_code NOT NULL,
  starter BOOLEAN NOT NULL,
  substitute BOOLEAN NOT NULL,
  PRIMARY KEY (tournament_id, match_id, team_id, player_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  CONSTRAINT starter_or_sub CHECK (
    (starter AND NOT substitute) OR (substitute AND NOT starter)
  ),
  CONSTRAINT shirt_number_valid CHECK (shirt_number BETWEEN 1 AND 99),
  CONSTRAINT home_or_away CHECK (home_team != away_team)
);

Manager appearances

One record per manager per team per match per tournament. Teams with co-managers will have two rows per match.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
team_iduuidThe unique ID number for the team of the manager. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
home_teambooleanWhether the team was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team was the away team. Coded 1 if the team was the away team and 0 otherwise.
manager_iduuidThe unique ID number for the manager. References manager_id in the managers table.
family_nametextThe family name of the manager.
given_nametextThe given name of the manager.
country_nametextThe name of the manager's home country.

SQL schema

CREATE TABLE worldcupsai.manager_appearances(
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  manager_id UUID NOT NULL,
  PRIMARY KEY (tournament_id, match_id, team_id, manager_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (manager_id) REFERENCES worldcupsai.managers (manager_id),
  CONSTRAINT home_or_away CHECK (home_team != away_team)
);

Referee appearances

One record per referee per match per tournament.
VariableTypeDescriptionRelPKFK
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
referee_iduuidThe unique ID number for the referee. References referee_id in the referees table.
family_nametextThe family name of the referee.
given_nametextThe given name of the referee.
country_nametextThe name of the referee's home country.
confederation_iduuidThe unique ID number for the confederation.
confederation_nametextThe name of the confederation.
confederation_codetextThe abbreviation for the confederation.

SQL schema

CREATE TABLE worldcupsai.referee_appearances(
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  referee_id UUID NOT NULL,
  PRIMARY KEY (tournament_id, match_id, referee_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (referee_id) REFERENCES worldcupsai.referees (referee_id)
);

Goals

One record per goal. Does not include converted penalties in penalty shootouts — those are in penalty_kicks. Does include penalties scored during the run of play (marked with penalty = TRUE).
For own goals, team_id records the team that is credited with the goal (i.e., the team that benefits), while player_team_id records the team that the player who scored the own goal belongs to. These are the same for regular goals, and different for own goals.
VariableTypeDescriptionRelPKFK
goal_iduuidThe unique ID number for the goal.
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
team_iduuidThe unique ID number for the team credited with the goal. For own goals, this is the team awarded the goal, not the team of the player who scored it. References team_id in the teams table.
team_nametextThe name of the team credited with the goal.
team_codetextThe 3-letter code for the team credited with the goal.
home_teambooleanWhether the team credited with the goal was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team credited with the goal was the away team. Coded 1 if the team was the away team and 0 otherwise.
player_iduuidThe unique ID number for the player who scored the goal. References player_id in the players table.
family_nametextThe family name of the player who scored the goal.
given_nametextThe given name of the player who scored the goal.
shirt_numberintegerThe shirt number of the player who scored the goal.
player_team_iduuidThe unique ID number for the team of the player who scored the goal. For own goals, this is the team of the player who scored, not the team credited with the goal. References team_id in the teams table.
player_team_nametextThe name of the team of the player who scored the goal.
player_team_codetextThe 3-letter code for the team of the player who scored the goal.
minute_labeltextThe minute of the match in which the goal occurred, in the format #' or #'+#'.
minute_regulationintegerThe minute of regulation time in which the goal occurred.
minute_stoppageintegerThe minute of stoppage time in which the goal occurred. Null if the goal did not occur during stoppage time.
match_periodenumThe period of the match in which the goal occurred. Possible values: first half, first half, stoppage time, second half, second half, stoppage time, extra time, first half, extra time, first half, stoppage time, extra time, second half, extra time, second half, stoppage time.
own_goalbooleanWhether the goal was an own goal. Coded 1 if the goal was an own goal and 0 otherwise.
penaltybooleanWhether the goal was scored from a penalty during the match (not a penalty shootout). Coded 1 if the goal was a penalty and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.goals(
  goal_id UUID NOT NULL,
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  player_id UUID NOT NULL,
  shirt_number INTEGER NOT NULL,
  player_team_id UUID NOT NULL,
  minute_label TEXT NOT NULL,
  minute_regulation INTEGER NOT NULL,
  minute_stoppage INTEGER,
  match_period worldcupsai.match_period NOT NULL,
  own_goal BOOLEAN NOT NULL,
  penalty BOOLEAN NOT NULL,
  PRIMARY KEY (goal_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  FOREIGN KEY (player_team_id) REFERENCES worldcupsai.teams (team_id),
  CONSTRAINT minutes_non_negative CHECK (minute_regulation >= 0),
  CONSTRAINT home_or_away CHECK (home_team != away_team),
  CONSTRAINT stoppage_only_in_stoppage_time CHECK (
    (minute_stoppage IS NULL) OR
    (match_period IN (
      'first half, stoppage time', 'second half, stoppage time',
      'extra time, first half, stoppage time',
      'extra time, second half, stoppage time'
    ))
  ),
  CONSTRAINT stoppage_required_in_stoppage_periods CHECK (
    (match_period NOT IN (
      'first half, stoppage time', 'second half, stoppage time',
      'extra time, first half, stoppage time',
      'extra time, second half, stoppage time'
    )) OR (minute_stoppage IS NOT NULL AND minute_stoppage > 0)
  )
);

Bookings

One record per booking. Coverage starts in 1970; the modern yellow/red card system was introduced at the 1970 World Cup. Exactly one of yellow_card, red_card, or second_yellow_card is TRUE for each booking. The sending_off variable is derived: it is always TRUE when red_card or second_yellow_card is TRUE.
VariableTypeDescriptionRelPKFK
booking_iduuidThe unique ID number for the booking.
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
team_iduuidThe unique ID number for the team of the player who was booked. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
home_teambooleanWhether the team was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team was the away team. Coded 1 if the team was the away team and 0 otherwise.
player_iduuidThe unique ID number for the player who was booked. References player_id in the players table.
family_nametextThe family name of the player who was booked.
given_nametextThe given name of the player who was booked.
shirt_numberintegerThe shirt number of the player who was booked.
minute_labeltextThe minute of the match in which the booking occurred, in the format #' or #'+#'.
minute_regulationintegerThe minute of regulation time in which the booking occurred.
minute_stoppageintegerThe minute of stoppage time in which the booking occurred. Null if the booking did not occur during stoppage time.
match_periodenumThe period of the match in which the booking occurred. Possible values: first half, first half, stoppage time, second half, second half, stoppage time, extra time, first half, extra time, first half, stoppage time, extra time, second half, extra time, second half, stoppage time.
yellow_cardbooleanWhether the booking was a yellow card. Coded 1 if the booking was a yellow card and 0 otherwise.
red_cardbooleanWhether the booking was a direct red card. Coded 1 if the booking was a red card and 0 otherwise.
second_yellow_cardbooleanWhether the booking was a second yellow card resulting in a dismissal. Coded 1 if the booking was a second yellow card and 0 otherwise.
sending_offbooleanWhether the booking resulted in the player being sent off. Coded 1 if the player was sent off and 0 otherwise. Derived from red_card and second_yellow_card.

SQL schema

CREATE TABLE worldcupsai.bookings(
  booking_id UUID NOT NULL,
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  player_id UUID NOT NULL,
  shirt_number INTEGER NOT NULL,
  minute_label TEXT NOT NULL,
  minute_regulation INTEGER NOT NULL,
  minute_stoppage INTEGER,
  match_period worldcupsai.match_period NOT NULL,
  yellow_card BOOLEAN NOT NULL,
  red_card BOOLEAN NOT NULL,
  second_yellow_card BOOLEAN NOT NULL,
  sending_off BOOLEAN NOT NULL,
  PRIMARY KEY (booking_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  CONSTRAINT one_card_type CHECK (
    (yellow_card::int + red_card::int + second_yellow_card::int) = 1
  ),
  CONSTRAINT home_or_away CHECK (home_team != away_team),
  CONSTRAINT sending_off_consistent CHECK (
    sending_off = (red_card OR second_yellow_card)
  ),
  CONSTRAINT minutes_non_negative CHECK (minute_regulation >= 0),
  CONSTRAINT stoppage_only_in_stoppage_time CHECK (
    (minute_stoppage IS NULL) OR
    (match_period IN (
      'first half, stoppage time', 'second half, stoppage time',
      'extra time, first half, stoppage time',
      'extra time, second half, stoppage time'
    ))
  ),
  CONSTRAINT stoppage_required_in_stoppage_periods CHECK (
    (match_period NOT IN (
      'first half, stoppage time', 'second half, stoppage time',
      'extra time, first half, stoppage time',
      'extra time, second half, stoppage time'
    )) OR (minute_stoppage IS NOT NULL AND minute_stoppage > 0)
  )
);

Substitutions

One record per player per substitution. Coverage starts in 1970. There are two rows per substitution event: one with going_off = TRUE for the player leaving the pitch and one with coming_on = TRUE for the player entering.
VariableTypeDescriptionRelPKFK
substitution_iduuidThe unique ID number for the substitution.
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
team_iduuidThe unique ID number for the team of the player who was substituted. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
home_teambooleanWhether the team was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team was the away team. Coded 1 if the team was the away team and 0 otherwise.
player_iduuidThe unique ID number for the player who was substituted. References player_id in the players table.
family_nametextThe family name of the player who was substituted.
given_nametextThe given name of the player who was substituted.
shirt_numberintegerThe shirt number of the player who was substituted.
minute_labeltextThe minute of the match in which the substitution occurred, in the format #' or #'+#'.
minute_regulationintegerThe minute of regulation time in which the substitution occurred.
minute_stoppageintegerThe minute of stoppage time in which the substitution occurred. Null if the substitution did not occur during stoppage time.
match_periodenumThe period of the match in which the substitution occurred. Possible values: first half, first half, stoppage time, second half, second half, stoppage time, extra time, first half, extra time, first half, stoppage time, extra time, second half, extra time, second half, stoppage time.
going_offbooleanWhether the player was going off the field. Coded 1 if the player was going off and 0 otherwise.
coming_onbooleanWhether the player was coming on the field. Coded 1 if the player was coming on and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.substitutions(
  substitution_id UUID NOT NULL,
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  player_id UUID NOT NULL,
  shirt_number INTEGER NOT NULL,
  minute_label TEXT NOT NULL,
  minute_regulation INTEGER NOT NULL,
  minute_stoppage INTEGER,
  match_period worldcupsai.match_period NOT NULL,
  going_off BOOLEAN NOT NULL,
  coming_on BOOLEAN NOT NULL,
  PRIMARY KEY (substitution_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  CONSTRAINT one_direction CHECK (going_off != coming_on),
  CONSTRAINT minutes_non_negative CHECK (minute_regulation >= 0),
  CONSTRAINT home_or_away CHECK (home_team != away_team),
  CONSTRAINT stoppage_only_in_stoppage_time CHECK (
    (minute_stoppage IS NULL) OR
    (match_period IN (
      'first half, stoppage time', 'second half, stoppage time',
      'extra time, first half, stoppage time',
      'extra time, second half, stoppage time'
    ))
  ),
  CONSTRAINT stoppage_required_in_stoppage_periods CHECK (
    (match_period NOT IN (
      'first half, stoppage time', 'second half, stoppage time',
      'extra time, first half, stoppage time',
      'extra time, second half, stoppage time'
    )) OR (minute_stoppage IS NOT NULL AND minute_stoppage > 0)
  )
);

Penalty kicks

One record per penalty kick in a penalty shootout. Does not include penalties taken during the run of play (those are in goals with penalty = TRUE). The order of kicks within a shootout is preserved by the order of rows.
VariableTypeDescriptionRelPKFK
penalty_kick_iduuidThe unique ID number for the penalty kick.
tournament_iduuidThe unique ID number for the tournament. References tournament_id in the tournaments table.
tournament_nametextThe name of the tournament.
match_iduuidThe unique ID number for the match. References match_id in the matches table.
match_nametextThe name of the match.
match_datedateThe date of the match.
stage_nameenumThe stage of the tournament.
group_nameenumThe name of the group.
team_iduuidThe unique ID number for the team of the player who took the penalty kick. References team_id in the teams table.
team_nametextThe name of the team.
team_codetextThe 3-letter code for the team.
home_teambooleanWhether the team was the home team. Coded 1 if the team was the home team and 0 otherwise.
away_teambooleanWhether the team was the away team. Coded 1 if the team was the away team and 0 otherwise.
player_iduuidThe unique ID number for the player who took the penalty kick. References player_id in the players table.
family_nametextThe family name of the player.
given_nametextThe given name of the player.
shirt_numberintegerThe shirt number of the player who took the penalty kick.
convertedbooleanWhether the penalty kick was converted. Coded 1 if the penalty kick was converted and 0 otherwise.

SQL schema

CREATE TABLE worldcupsai.penalty_kicks(
  penalty_kick_id UUID NOT NULL,
  tournament_id UUID NOT NULL,
  match_id UUID NOT NULL,
  team_id UUID NOT NULL,
  home_team BOOLEAN NOT NULL,
  away_team BOOLEAN NOT NULL,
  player_id UUID NOT NULL,
  shirt_number INTEGER NOT NULL,
  converted BOOLEAN NOT NULL,
  PRIMARY KEY (penalty_kick_id),
  FOREIGN KEY (tournament_id) REFERENCES worldcupsai.tournaments (tournament_id),
  FOREIGN KEY (match_id) REFERENCES worldcupsai.matches (match_id),
  FOREIGN KEY (team_id) REFERENCES worldcupsai.teams (team_id),
  FOREIGN KEY (player_id) REFERENCES worldcupsai.players (player_id),
  CONSTRAINT home_or_away CHECK (home_team != away_team)
);

Coverage Table

Not all tables cover all time periods. The table below summarizes the coverage of each table.
TableCoverageNotes
tournaments1930 – presentComplete
confederations1930 – presentComplete
teams1930 – presentComplete
players1930 – presentComplete
managers1930 – presentComplete
referees1930 – presentComplete
stadiums1930 – presentComplete
awards1930 – presentComplete
qualified_teams1930 – presentComplete
host_countries1930 – presentComplete
tournament_stages1930 – presentComplete
groups1930 – presentNo records for 1934 and 1938, which had no group stage
group_standings1930 – presentNo records for 1934 and 1938, which had no group stage
tournament_standings1930 – presentOnly top 4 positions recorded (top 2 if no third-place match)
squads1930 – presentComplete
manager_appointments1930 – presentComplete
referee_appointments1930 – presentComplete
matches1930 – presentComplete
team_appearances1930 – presentComplete
player_appearances1970 – presentNo records before 1970
manager_appearances1930 – presentComplete
referee_appearances1930 – presentComplete
goals1930 – presentComplete
penalty_kicks1978 – presentPenalty shootouts were introduced in 1978
bookings1970 – presentNo records before 1970
substitutions1970 – presentNo records before 1970
award_winners1930 – presentAwards were introduced at different points
shirt_number1954 - presentIntroduced in 1954 (used inconsistently in 1950)

Data Notes

Data sources

All data in the database is coded from Wikipedia. Some information is cross-referenced with the official FIFA match reports. The Wikipedia pages used to code the database are archived in the data-raw/ folder of the repository. Tournament and award data is hand-coded; squad and match data is machine-coded using R scripts available in data-raw/code/. All replication code is open source and available in the repository.

Player names

The official FIFA match reports contain numerous errors in player names, including incorrect given names, misspellings, and inconsistent or outdated transliterations. Wikipedia provides cleaner names. Player names are not always uniquely identifying — the database uses Wikipedia links as the primary identifier for players, standardized so that each player has exactly one link across all tournaments. Players commonly known by a single name (e.g., Pelé, Ronaldinho) have that name recorded in family_name and a null given_name. The replication code performs extensive name standardization, including resolving inconsistent names for players who appeared across multiple tournaments under different transliterations or married names.

Shirt numbers

Prior to 1950, players did not wear numbers on their shirts. In the 1950 World Cup, players had numbers but they were not consistent from match to match. The shirt_number variable in squads is coded 0 for all tournaments prior to 1954.

Substitution patterns

Data on substitutions and player appearances starts in 1970. FIFA match reports before 1970 do not record substitution information, so pre-1970 lineups cannot be fully reconstructed. The substitutions and player_appearances tables therefore have no records before 1970. When comparing the data to online versions of the official FIFA match reports, note that the match reports sometimes incorrectly swap the player going off and the player coming on, or record the wrong player as coming on. These errors have been corrected where identified.

Event times

For goals, substitutions, and bookings, times are taken from Wikipedia. These can differ slightly from the official FIFA match reports, which themselves sometimes contain inconsistencies. It is common for the exact minute of in-match events to vary slightly between different sources.

Match minutes

In football, minute 1' refers to the time from 00:00 to 00:59; minute 45' refers to the time from 44:00 to 44:59 and is the last regulation minute of the first half; minute 46' is the first regulation minute of the second half. Stoppage time minutes are written as 45'+1', 45'+2', etc. for first-half stoppage time, and 90'+1', 90'+2', etc. for second-half stoppage time. The same pattern continues into extra time (91'105' for the first half of extra time, 106'120' for the second half).
In this database, minute_regulation records the number before the + and minute_stoppage records the number after the +. For events in regulation time, minute_stoppage is null. For events in stoppage time, minute_regulation will be 45, 90, 105, or 120, and minute_stoppage will be 1 or greater.

Tournament formats

The format of the World Cup has changed significantly over time. Key variations:
  • The 1934 and 1938 tournaments were purely knockout from the first round with no group stage. The groups table has no records for these tournaments.
  • The 1950 tournament used a round-robin final group instead of a final match; the winner was determined by points, not a single final.
  • Through 1970, group names used numbers (Group 1, Group 2, etc.).
  • The 1974, 1978, and 1982 tournaments had two group stages. The groups and group_standings tables include records for both stages. In 1974 and 1978, the first stage used numbered groups and the second stage used lettered groups. In 1982, both stages used numbered groups.
  • From 1986 onward, group names have used letters (Group A, Group B, etc.).

Country codes

The database uses ISO Alpha-3 country codes. Notable cases:
  • The home nations use their own codes: ENG (England), WAL (Wales), SCO (Scotland), NIR (Northern Ireland).
  • Former countries: SCG (Serbia and Montenegro), YUG (Yugoslavia), DDR (East Germany), CSK (Czechoslovakia), SUN (Soviet Union).
  • The Dutch East Indies uses IDN (Indonesia) as there is no ISO Alpha-3 code for the Dutch East Indies.
  • West Germany uses the code for Germany (DEU) but is labeled as "West Germany" in the team_name field.

Points systems

The group_standings table records points as awarded in the original tournament. Tournaments from 1994 onward use the modern 3-1-0 system (3 points for a win, 1 for a draw, 0 for a loss). Earlier tournaments used the 2-1-0 system (2 points for a win, 1 for a draw, 0 for a loss). Both systems are valid values and are preserved as-is.

Replayed matches

A small number of matches were replayed due to exceptional circumstances (e.g., the 1930 and 1934 tournaments). The original match has replayed = TRUE and the replacement match has replay = TRUE. Both records are included in the matches table. Goal and event data are recorded only for the matches that count toward the official record.

Known limitations

The database does not include: assistant referees, fourth officials, or video assistant referees; player club affiliations; qualifying matches; friendly matches; or any matches outside of the official FIFA World Cup tournaments. Data quality is high, and data is extensively audited and cross-checked where possible, but reflects the quality of the underlying Wikipedia sources. Known errors in Wikipedia have been corrected where identified, but others may remain.

Frequently Asked Questions

Why does a goal have two team ID columns — team_id and player_team_id? For regular goals, these are the same. For own goals, they differ. team_id records the team credited with the goal — the team that benefits. player_team_id records the team of the player who actually scored it. When analyzing a player's goal tally, always filter on player_team_id and exclude own_goal = TRUE. When analyzing a team's goals scored, use team_id.
Why do some players have a null value for given_name? Players commonly known by a single name — Pelé, Ronaldinho, Eusébio, and others — have that name recorded in family_name and a null in given_name. This allows you to use family_name as a display label.
Are West Germany and Germany the same team? No. West Germany and Germany are different countries. They are recorded as separate entries in the teams table. West Germany (DEU, labeled "West Germany") and the reunified Germany (DEU, labeled "Germany") share the same ISO country code but are distinct rows with different team_id values. Be careful when aggregating across both — you will need to decide whether to treat them as the same entity for historical analyses.
What does "home team" mean? The home_team designation follows the official FIFA match designation — it is the first-named team in the official record, not necessarily a team playing in their home country. In most cases this distinction is not meaningful. Do not use home_team as a proxy for whether a team was playing in their own country; use host_countries for that.
Why do player_appearances, bookings, and substitutions start in 1970? FIFA match reports before 1970 do not record lineup or substitution information in a form that can be reliably coded. The yellow and red card system was also introduced at the 1970 World Cup. These tables therefore have no records for tournaments before 1970. squads is available for all tournaments, so you can identify which players were registered for pre-1970 tournaments even if their match-level appearances are not available.
Why does the 1950 tournament not have a final? The 1950 World Cup used a round-robin final group rather than a knockout final. The winner was determined by points accumulated across the final group matches, not by a single final match. The final column in tournaments is FALSE for 1950, and tournament_standings records only the top positions based on the final group standings.
How do I find which team won a tournament? Use tournament_standings and filter for position = 1. Do not rely on host_won in tournaments for this — that variable only indicates whether the host country won, not which team did.
Why do some tournaments have two group stages? The 1974, 1978, and 1982 World Cups used a second group stage instead of a knockout round of 16. The groups and group_standings tables include records for both stages, identified by stage_number and stage_name. The tournament_stages table documents the full structure of each tournament.
How should I handle players who represented different countries in different tournaments? A small number of players represented different national teams across tournaments — typically due to geopolitical changes or eligibility switches. These players appear only once in the players table, with a single player_id that links all their appearances. Their squads and player_appearances records will show different team_id values across tournaments. This is intentional and correct.
Why are there no records for the 1934 and 1938 tournaments in groups and group_standings? Those tournaments were purely knockout from the first round with no group stage. There are no groups to record. The tournaments table has group_stage = FALSE for both.
What is the difference between squad_position in squads and position in player_appearances? squads uses a coarse four-value position system for squad registration: goal keeper, defender, midfielder, forward. player_appearances uses a granular 21-value system reflecting the player's actual position in each match. These are different typologies that serve different purposes.