Data Dictionary

Everything you need to know about the data behind KickStat. All tables live in the understat_reporting schema.

Source: Understat (understat.com)Shot-level data from 6 European leagues: EPL (Premier League), La_Liga, Bundesliga, Serie_A, Ligue_1, and RFPL.

One row per shot

understat_reporting.shots

The core granularity of the dataset — every shot from every match across the top 6 European leagues.

ColumnTypeDescription
shot_idintUnique identifier for the shot.
match_idintMatch this shot belongs to — join to match_details for match context.
player_idintUnderstat ID of the player who took the shot.
playertextName of the player who took the shot.
h_atextWhether the shooting player's team was home (h) or away (a).
minuteintMinute of the match when the shot was taken (0–90+).
resulttextOutcome of the shot: Goal, SavedShot, BlockedShot, MissedShots, ShotOnPost, or OwnGoal.
xfloatHorizontal position of the shot on the pitch (0.0 = own goal line, 1.0 = opponent goal line).
yfloatVertical position of the shot on the pitch (0.0 = left touchline, 1.0 = right touchline).
xgfloatExpected Goals — the probability (0.0–1.0) that this shot results in a goal, based on historical shot data from similar positions and situations. A header from 6 yards out might be 0.7 xG; a long-range volley might be 0.03.
situationtextRaw situation type from Understat: OpenPlay, FromCorner, SetPiece, DirectFreekick, Penalty.
shot_typetextBody part used: RightFoot, LeftFoot, Head.
last_actiontextThe action immediately before the shot (e.g., Pass, Cross, Dribble, Rebound, BallRecovery).
player_assistedtextName of the player who provided the assist or key pass (null if unassisted).
shot_fortextName of the team credited with the shot (accounts for own goals).
shot_againsttextName of the defending team.
seasontextSeason year as a string (e.g., "2024", "2025") — not an integer.
datedateDate the match was played.
play_statustextNormalized play type: open_play, set_piece, or penalty. Simplifies the raw situation field into three categories.
distance_to_goal_metersfloatStraight-line distance from the shot location to the center of the goal, in meters. Calculated from x/y coordinates using real pitch dimensions (105m x 68m).
shot_for_team_idintTeam ID of the team credited with the shot.
shot_against_team_idintTeam ID of the defending team.
assisting_player_idintPlayer ID of the assisting player (null if unassisted).

Common joins

Add league and team names to shots
FROM understat_reporting.shots s
JOIN understat_reporting.match_details m ON s.match_id = m.match_id
WHERE m.league = 'EPL' AND m.season = '2025'

Example queries

Top 10 players by total xG this season
SELECT player, COUNT(*) as shots, ROUND(SUM(xg)::numeric, 2) as total_xg
FROM understat_reporting.shots s
JOIN understat_reporting.match_details m ON s.match_id = m.match_id
WHERE m.league = 'EPL' AND m.season = '2025'
GROUP BY player
ORDER BY total_xg DESC
LIMIT 10
Shot conversion rate by play type
SELECT play_status,
       COUNT(*) as shots,
       COUNT(CASE WHEN result = 'Goal' THEN 1 END) as goals,
       ROUND(COUNT(CASE WHEN result = 'Goal' THEN 1 END)::numeric / COUNT(*)::numeric * 100, 1) as conversion_pct
FROM understat_reporting.shots s
JOIN understat_reporting.match_details m ON s.match_id = m.match_id
WHERE m.league = 'EPL' AND m.season = '2025'
GROUP BY play_status
ORDER BY conversion_pct DESC
One row per match

understat_reporting.match_details

Match-level metadata, scores, expected goals, and goal breakdowns by play type.

ColumnTypeDescription
match_idintUnique match identifier.
leaguetextLeague abbreviation as stored in the database: EPL (Premier League), La_Liga, Bundesliga, Serie_A, Ligue_1, or RFPL (Russian Premier League). Use the abbreviation in WHERE clauses — e.g., WHERE league = 'EPL'.
seasontextSeason year as a string (e.g., "2025").
datetimetimestampDate and kickoff time of the match.
forecast_home_winfloatPre-match probability of a home win (from Understat's model).
forecast_away_winfloatPre-match probability of an away win.
home_team_idintTeam ID of the home team.
home_teamtextFull name of the home team.
home_abbreviationtextShort name / abbreviation of the home team (e.g., ARS for Arsenal).
away_team_idintTeam ID of the away team.
away_teamtextFull name of the away team.
away_abbreviationtextShort name / abbreviation of the away team.
is_resultboolWhether the match has been played and has a final result. false for scheduled future fixtures.
home_goalsintGoals scored by the home team.
home_team_open_play_goalsintHome goals scored from open play.
home_team_set_piece_goalsintHome goals from set pieces (corners, free kicks).
home_team_penalty_goalsintHome goals from penalties.
home_xgfloatTotal expected goals for the home team across all shots in the match.
away_goalsintGoals scored by the away team.
away_team_open_play_goalsintAway goals scored from open play.
away_team_set_piece_goalsintAway goals from set pieces.
away_team_penalty_goalsintAway goals from penalties.
away_xgfloatTotal expected goals for the away team.

Example queries

Matches where the underdog won (lower xG team won)
SELECT home_team, away_team, home_goals, away_goals,
       ROUND(home_xg::numeric, 2) as home_xg, ROUND(away_xg::numeric, 2) as away_xg
FROM understat_reporting.match_details
WHERE league = 'EPL' AND season = '2025' AND is_result = true
  AND ((home_goals > away_goals AND home_xg < away_xg)
    OR (away_goals > home_goals AND away_xg < home_xg))
ORDER BY datetime DESC
One row per team / season / league

understat_reporting.standings

League table standings calculated from match results.

ColumnTypeDescription
leaguetextLeague abbreviation: EPL, La_Liga, Bundesliga, Serie_A, Ligue_1, or RFPL.
seasontextSeason year.
team_idintTeam identifier.
titletextFull team name.
short_titletextTeam abbreviation.
games_playedintNumber of matches played.
pointsintTotal league points (3 for a win, 1 for a draw, 0 for a loss).
goals_forintTotal goals scored.
goals_againstintTotal goals conceded.
goal_differentialintGoals scored minus goals conceded — a key tiebreaker in most leagues.
placeintLeague position (1 = top of the table). Ranked by points, then goal differential.

Example queries

Current Premier League table
SELECT place, title, games_played, points, goals_for, goals_against, goal_differential
FROM understat_reporting.standings
WHERE league = 'EPL' AND season = '2025'
ORDER BY place
One row per team / season / league

understat_reporting.team_season_xg_summary

Aggregated expected goals metrics broken down by play type — useful for understanding how a team generates its attacking output.

ColumnTypeDescription
seasontextSeason year.
leaguetextLeague abbreviation: EPL, La_Liga, Bundesliga, Serie_A, Ligue_1, or RFPL.
team_idintTeam identifier.
titletextFull team name.
short_titletextTeam abbreviation.
placeintLeague position from standings.
games_playedintMatches played.
pointsintLeague points.
shotsintTotal shots taken.
total_xgfloatTotal expected goals across all shots.
open_play_xgfloatxG from open play situations only.
open_play_xg_pctfloatPercentage of total xG that came from open play (0.0–1.0).
set_piece_xgfloatxG from set pieces (corners, free kicks).
set_piece_xg_pctfloatPercentage of total xG from set pieces.
penalty_xgfloatxG from penalties.
penalty_xg_pctfloatPercentage of total xG from penalties.
total_non_penalty_xgfloatTotal xG excluding penalties — often called npxG. A cleaner measure of a team's shot quality since penalties are high-xG events that don't reflect build-up play.
non_penalty_open_play_xg_pctfloatShare of non-penalty xG from open play.
non_penalty_set_piece_xg_pctfloatShare of non-penalty xG from set pieces.

Example queries

Teams most reliant on set pieces for xG (non-penalty)
SELECT title, ROUND(total_non_penalty_xg::numeric, 1) as npxg,
       ROUND(non_penalty_set_piece_xg_pct::numeric * 100, 1) as set_piece_pct
FROM understat_reporting.team_season_xg_summary
WHERE league = 'EPL' AND season = '2025'
ORDER BY non_penalty_set_piece_xg_pct DESC
One row per player per match

understat_reporting.player_match_stats

The most detailed table — individual player performance, shot breakdowns by play type, assist metrics, and on-field impact (team performance with vs. without the player).

Identity & context

ColumnTypeDescription
player_idintPlayer identifier.
playertextPlayer name.
team_idintTeam identifier.
team_nametextFull team name.
team_short_nametextTeam abbreviation.
match_idintMatch identifier.
leaguetextLeague name.
seasontextSeason year.
roster_entry_idintUnique ID for this player's roster entry in this match.
h_atextHome (h) or away (a).
positiontextPlayer's position in this match (e.g., FW, MC, DC, Sub).
appearance_typetextHow the player featured: start, sub (came off the bench), unused (on the bench but didn't play), absent (not in the squad).

Match context

ColumnTypeDescription
datetimetimestampMatch date and time.
is_resultboolMatch completed.
home_teamtextHome team name.
away_teamtextAway team name.
opponenttextName of the opposing team.
opponent_idintOpposing team ID.
venuetexthome or away.
team_goals_forintGoals scored by the player's team.
team_goals_againstintGoals conceded by the player's team.
team_xg_forfloatTeam's total xG in this match.
team_xg_againstfloatOpponent's total xG in this match.
team_open_play_goalsintTeam's open play goals.
team_set_piece_goalsintTeam's set piece goals.
team_penalty_goalsintTeam's penalty goals.
resulttextMatch result from the player's team perspective: W, D, or L.
points_earnedintLeague points earned: 3 (win), 1 (draw), 0 (loss).
opponent_league_positionintOpponent's league position — useful for filtering by opponent strength.
opponent_tiertextOpponent strength bucket: top_6, mid_table, or bottom_6.

Base performance

ColumnTypeDescription
minutes_playedintMinutes on the field (0 if absent or unused).
goalsintGoals scored.
own_goalsintOwn goals.
shotsintTotal shots taken.
xgfloatTotal expected goals from the player's shots.
key_passesintPasses that directly led to a shot by a teammate.
assistsintPasses that directly led to a goal.
xafloatExpected Assists — the probability that each key pass leads to a goal, summed across the match. Like xG but for the passer.
xg_chainfloatxG Chain — total xG of all possessions the player was involved in. Measures overall attacking involvement.
xg_buildupfloatxG Buildup — like xG Chain but excludes the shot-taker and assister. Measures contribution to build-up play without the final ball or shot.
yellow_cardboolReceived a yellow card.
red_cardboolReceived a red card.
roster_inintMinute the player came on as a sub (null if started).
roster_outintMinute the player was subbed off (null if played the full match).

Shot breakdown by play type

ColumnTypeDescription
open_play_xgfloatxG from open play shots.
open_play_shotsintNumber of open play shots.
open_play_goals_scoredintGoals scored from open play.
open_play_blocked_shotsintOpen play shots blocked by a defender.
open_play_missed_shotsintOpen play shots that missed the target entirely.
open_play_saved_shotsintOpen play shots saved by the goalkeeper.
open_play_shot_on_postintOpen play shots that hit the post or crossbar.
set_piece_xgfloatxG from set piece shots.
set_piece_shotsintNumber of set piece shots.
set_piece_goals_scoredintGoals scored from set piece.
set_piece_blocked_shotsintSet piece shots blocked by a defender.
set_piece_missed_shotsintSet piece shots that missed the target entirely.
set_piece_saved_shotsintSet piece shots saved by the goalkeeper.
set_piece_shot_on_postintSet piece shots that hit the post or crossbar.
penalty_xgfloatxG from penalty shots.
penalty_shotsintNumber of penalty shots.
penalty_goals_scoredintGoals scored from penalty.
penalty_blocked_shotsintPenalty shots blocked by a defender.
penalty_missed_shotsintPenalty shots that missed the target entirely.
penalty_saved_shotsintPenalty shots saved by the goalkeeper.
penalty_shot_on_postintPenalty shots that hit the post or crossbar.

Assist breakdown by play type

ColumnTypeDescription
open_play_xafloatExpected assists from open play.
open_play_key_passes_from_shotsintKey passes that led to open play shots.
open_play_assistsintAssists that led to goals from open play.
set_piece_xafloatExpected assists from set piece.
set_piece_key_passes_from_shotsintKey passes that led to set piece shots.
set_piece_assistsintAssists that led to goals from set piece.

On-field impact (attacking)

ColumnTypeDescription
team_open_play_xg_withfloatTeam's open play xG while the player was on the field.
team_open_play_xg_withoutfloatTeam's open play xG while the player was off the field.
team_set_piece_xg_withfloatTeam's set piece xG while the player was on the field.
team_set_piece_xg_withoutfloatTeam's set piece xG while the player was off the field.
team_penalty_xg_withfloatTeam's penalty xG while the player was on the field.
team_penalty_xg_withoutfloatTeam's penalty xG while the player was off the field.
team_goals_withintGoals the team scored while the player was on the field.
team_goals_withoutintGoals the team scored while the player was off the field.
team_total_xg_withfloatTeam's total xG while the player was on the field.
team_total_xg_withoutfloatTeam's total xG while the player was off the field.

On-field impact (defensive)

ColumnTypeDescription
team_open_play_xg_against_withfloatOpponent open play xG while the player was on the field.
team_open_play_xg_against_withoutfloatOpponent open play xG while the player was off the field.
team_set_piece_xg_against_withfloatOpponent set piece xG while the player was on the field.
team_set_piece_xg_against_withoutfloatOpponent set piece xG while the player was off the field.
team_penalty_xg_against_withfloatOpponent penalty xG while the player was on the field.
team_penalty_xg_against_withoutfloatOpponent penalty xG while the player was off the field.
team_goals_against_withintGoals conceded while the player was on the field.
team_goals_against_withoutintGoals conceded while the player was off the field.
team_total_xg_against_withfloatTotal opponent xG while the player was on the field.
team_total_xg_against_withoutfloatTotal opponent xG while the player was off the field.

Example queries

Saka's per-90 output this season
SELECT player,
       SUM(minutes_played) as total_mins,
       ROUND(SUM(xg)::numeric / (SUM(minutes_played)::numeric / 90), 2) as xg_per_90,
       ROUND(SUM(xa)::numeric / (SUM(minutes_played)::numeric / 90), 2) as xa_per_90,
       SUM(goals) as goals,
       SUM(assists) as assists
FROM understat_reporting.player_match_stats
WHERE player = 'Bukayo Saka' AND season = '2025' AND appearance_type IN ('start', 'sub')
GROUP BY player
Players whose teams create more xG when they play
SELECT player, team_name,
       COUNT(*) as matches,
       ROUND(AVG(team_total_xg_with)::numeric, 2) as avg_team_xg_with,
       ROUND(AVG(team_total_xg_without)::numeric, 2) as avg_team_xg_without,
       ROUND((AVG(team_total_xg_with) - AVG(team_total_xg_without))::numeric, 2) as xg_diff
FROM understat_reporting.player_match_stats
WHERE league = 'EPL' AND season = '2025' AND appearance_type = 'start'
GROUP BY player, team_name
HAVING COUNT(*) >= 10
ORDER BY xg_diff DESC
LIMIT 10

Gotchas

  • Seasons are strings, not integers

    Use WHERE season = '2025' — not season = 2025. The column stores the year as text.

  • shots has no league column

    Join shots to match_details on match_id when you need to filter by league.

  • play_status simplifies situation

    play_status normalizes the raw situation field into three buckets: open_play, set_piece, penalty.

Query limits

  • SELECT only. 500 row max. 10 second timeout.
  • Seasons from 2014/15 to present.