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;