File size: 5,358 Bytes
7592718 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | -- nba-viz schema (Postgres). Run once: psql $DATABASE_URL -f db/schema.sql
-- Sized to fit a free tier (~0.5 GB): 5 seasons of logs + shot detail, no play-by-play.
CREATE TABLE IF NOT EXISTS players (
player_id INTEGER PRIMARY KEY,
full_name TEXT NOT NULL,
first_name TEXT,
last_name TEXT,
is_active BOOLEAN DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS teams (
team_id INTEGER PRIMARY KEY,
abbreviation TEXT NOT NULL,
nickname TEXT,
city TEXT,
full_name TEXT
);
CREATE TABLE IF NOT EXISTS team_game_logs (
team_id INTEGER NOT NULL,
game_id TEXT NOT NULL,
season TEXT NOT NULL, -- '2024-25'
season_type TEXT NOT NULL DEFAULT 'Regular Season',
game_date DATE NOT NULL,
matchup TEXT, -- 'GSW vs. LAL' / 'GSW @ LAL'
wl TEXT,
min NUMERIC,
fgm INTEGER, fga INTEGER, fg_pct NUMERIC,
fg3m INTEGER, fg3a INTEGER, fg3_pct NUMERIC,
ftm INTEGER, fta INTEGER, ft_pct NUMERIC,
oreb INTEGER, dreb INTEGER, reb INTEGER,
ast INTEGER, stl INTEGER, blk INTEGER, tov NUMERIC, pf INTEGER,
pts INTEGER, plus_minus NUMERIC,
PRIMARY KEY (team_id, game_id)
);
CREATE INDEX IF NOT EXISTS idx_tgl_season ON team_game_logs (season, season_type);
CREATE INDEX IF NOT EXISTS idx_tgl_date ON team_game_logs (game_date);
CREATE TABLE IF NOT EXISTS player_game_logs (
player_id INTEGER NOT NULL,
game_id TEXT NOT NULL,
player_name TEXT,
team_id INTEGER,
team_abbreviation TEXT,
season TEXT NOT NULL,
season_type TEXT NOT NULL DEFAULT 'Regular Season',
game_date DATE NOT NULL,
matchup TEXT,
wl TEXT,
min NUMERIC,
fgm INTEGER, fga INTEGER, fg_pct NUMERIC,
fg3m INTEGER, fg3a INTEGER, fg3_pct NUMERIC,
ftm INTEGER, fta INTEGER, ft_pct NUMERIC,
oreb INTEGER, dreb INTEGER, reb INTEGER,
ast INTEGER, stl INTEGER, blk INTEGER, tov NUMERIC, pf INTEGER,
pts INTEGER, plus_minus NUMERIC,
PRIMARY KEY (player_id, game_id)
);
CREATE INDEX IF NOT EXISTS idx_pgl_season ON player_game_logs (season, season_type);
CREATE INDEX IF NOT EXISTS idx_pgl_player ON player_game_logs (player_id, season);
CREATE INDEX IF NOT EXISTS idx_pgl_date ON player_game_logs (game_date);
-- Shot-level detail (x/y coordinates). ~220k rows per season.
CREATE TABLE IF NOT EXISTS shots (
game_id TEXT NOT NULL,
game_event_id INTEGER NOT NULL,
player_id INTEGER NOT NULL,
player_name TEXT,
team_id INTEGER,
season TEXT NOT NULL,
season_type TEXT NOT NULL DEFAULT 'Regular Season',
game_date DATE,
period INTEGER,
minutes_remaining INTEGER,
seconds_remaining INTEGER,
event_type TEXT, -- 'Made Shot' / 'Missed Shot'
action_type TEXT, -- 'Jump Shot', 'Driving Layup Shot', ...
shot_type TEXT, -- '2PT Field Goal' / '3PT Field Goal'
shot_zone_basic TEXT,
shot_zone_area TEXT,
shot_zone_range TEXT,
shot_distance INTEGER,
loc_x INTEGER, -- tenths of feet, -250..250 (court width)
loc_y INTEGER, -- tenths of feet, hoop at y=0
shot_made_flag INTEGER,
PRIMARY KEY (game_id, game_event_id)
);
CREATE INDEX IF NOT EXISTS idx_shots_player ON shots (player_id, season);
CREATE INDEX IF NOT EXISTS idx_shots_team ON shots (team_id, season);
-- Aggregate tracking: shooting splits by closest-defender distance bucket.
-- This is the public proxy for "conditioning on defenders" — raw positional
-- tracking data is not publicly available.
CREATE TABLE IF NOT EXISTS defender_shooting (
season TEXT NOT NULL,
player_id INTEGER NOT NULL,
player_name TEXT,
def_dist_range TEXT NOT NULL, -- '0-2 Feet - Very Tight', etc.
gp INTEGER,
fga_frequency NUMERIC,
fgm NUMERIC, fga NUMERIC, fg_pct NUMERIC, efg_pct NUMERIC,
fg2m NUMERIC, fg2a NUMERIC, fg2_pct NUMERIC,
fg3m NUMERIC, fg3a NUMERIC, fg3_pct NUMERIC,
PRIMARY KEY (season, player_id, def_dist_range)
);
CREATE TABLE IF NOT EXISTS standings (
season TEXT NOT NULL,
team_id INTEGER NOT NULL,
team_city TEXT,
team_name TEXT,
conference TEXT,
playoff_rank INTEGER,
wins INTEGER,
losses INTEGER,
win_pct NUMERIC,
updated_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (season, team_id)
);
-- Convenience view: per-player season averages (regular season).
CREATE OR REPLACE VIEW player_season_averages AS
SELECT
player_id,
max(player_name) AS player_name,
season,
count(*) AS gp,
avg(min) AS min,
avg(pts) AS pts,
avg(reb) AS reb,
avg(ast) AS ast,
avg(stl) AS stl,
avg(blk) AS blk,
avg(tov) AS tov,
avg(fg3m) AS fg3m,
avg(plus_minus) AS plus_minus,
sum(fgm)::numeric / nullif(sum(fga), 0) AS fg_pct,
sum(fg3m)::numeric / nullif(sum(fg3a), 0) AS fg3_pct,
sum(ftm)::numeric / nullif(sum(fta), 0) AS ft_pct
FROM player_game_logs
WHERE season_type = 'Regular Season'
GROUP BY player_id, season;
|