-- Nike Football Database Schema — TheSportsDB cache layer -- -- Caching strategy: -- PERMANENT: leagues, teams, players (change rarely, cache aggressively) -- SEMI-PERMANENT: finished match results, lineups, stats, timeline -- VOLATILE (NOT cached): standings, live scores, upcoming fixtures -- -- All IDs come from TheSportsDB (idLeague, idTeam, idEvent, idPlayer). -- No surrogate keys — TheSportsDB IDs are the primary keys. -- ══════════════════════════════════════════════════════════ -- Core reference tables (permanent cache) -- ══════════════════════════════════════════════════════════ CREATE TABLE IF NOT EXISTS leagues ( id INTEGER PRIMARY KEY, -- TheSportsDB idLeague name VARCHAR(255) NOT NULL, -- strLeague sport VARCHAR(50) DEFAULT 'Soccer', -- strSport country VARCHAR(100), -- strCountry badge_url VARCHAR(500), -- strBadge banner_url VARCHAR(500), -- strBanner description TEXT, -- strDescriptionEN is_followed BOOLEAN DEFAULT FALSE, cached_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS teams ( id INTEGER PRIMARY KEY, -- TheSportsDB idTeam name VARCHAR(255) NOT NULL, -- strTeam short_name VARCHAR(10), -- strTeamShort alternate_name VARCHAR(255), -- strTeamAlternate league_id INTEGER, -- idLeague (primary), no FK — cached opportunistically league_name VARCHAR(255), -- strLeague formed_year INTEGER, -- intFormedYear sport VARCHAR(50) DEFAULT 'Soccer', country VARCHAR(100), -- strCountry stadium VARCHAR(255), -- strStadium stadium_capacity INTEGER, -- intStadiumCapacity location VARCHAR(255), -- strLocation venue_id INTEGER, -- idVenue badge_url VARCHAR(500), -- strBadge logo_url VARCHAR(500), -- strLogo banner_url VARCHAR(500), -- strBanner equipment_url VARCHAR(500), -- strEquipment colour1 VARCHAR(20), -- strColour1 colour2 VARCHAR(20), -- strColour2 colour3 VARCHAR(20), -- strColour3 website VARCHAR(255), -- strWebsite description TEXT, -- strDescriptionEN is_followed BOOLEAN DEFAULT FALSE, cached_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS players ( id INTEGER PRIMARY KEY, -- TheSportsDB idPlayer team_id INTEGER, -- idTeam, no FK — cached opportunistically team_name VARCHAR(255), -- strTeam name VARCHAR(255) NOT NULL, -- strPlayer nationality VARCHAR(100), -- strNationality date_of_birth DATE, -- dateBorn position VARCHAR(50), -- strPosition squad_number VARCHAR(10), -- strNumber height VARCHAR(20), -- strHeight weight VARCHAR(20), -- strWeight gender VARCHAR(10), -- strGender status VARCHAR(30), -- strStatus (Active/Retired) thumb_url VARCHAR(500), -- strThumb cutout_url VARCHAR(500), -- strCutout description TEXT, -- strDescriptionEN cached_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ══════════════════════════════════════════════════════════ -- Match results (cached when strStatus = 'Match Finished') -- ══════════════════════════════════════════════════════════ CREATE TABLE IF NOT EXISTS events ( id INTEGER PRIMARY KEY, -- TheSportsDB idEvent name VARCHAR(255), -- strEvent league_id INTEGER, -- idLeague, no FK — cached opportunistically league_name VARCHAR(255), -- strLeague season VARCHAR(20), -- strSeason round VARCHAR(20), -- intRound home_team_id INTEGER, -- idHomeTeam, no FK — cached opportunistically away_team_id INTEGER, -- idAwayTeam, no FK — cached opportunistically home_team VARCHAR(255), -- strHomeTeam away_team VARCHAR(255), -- strAwayTeam home_score INTEGER, -- intHomeScore away_score INTEGER, -- intAwayScore event_date DATE, -- dateEvent event_time TIME, -- strTime event_timestamp TIMESTAMPTZ, -- strTimestamp venue VARCHAR(255), -- strVenue venue_id INTEGER, -- idVenue city VARCHAR(100), -- strCity country VARCHAR(100), -- strCountry referee VARCHAR(255), -- strOfficial spectators INTEGER, -- intSpectators status VARCHAR(50), -- strStatus postponed VARCHAR(5) DEFAULT 'no', -- strPostponed poster_url VARCHAR(500), -- strPoster thumb_url VARCHAR(500), -- strThumb video_url VARCHAR(500), -- strVideo cached_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ── Match detail sub-tables (V2, cached for finished matches) ── CREATE TABLE IF NOT EXISTS event_stats ( id INTEGER PRIMARY KEY, -- TheSportsDB idStatistic event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE, id_api_football VARCHAR(20), -- idApiFootball event_name VARCHAR(255), -- strEvent stat_name VARCHAR(100) NOT NULL, -- strStat (e.g. "Shots on Goal") home_value VARCHAR(50), -- intHome away_value VARCHAR(50), -- intAway UNIQUE(event_id, stat_name) ); CREATE TABLE IF NOT EXISTS event_lineup ( id INTEGER PRIMARY KEY, -- TheSportsDB idLineup event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE, id_api_football VARCHAR(20), -- idAPIfootball event_name VARCHAR(255), -- strEvent player_id INTEGER, -- idPlayer player_name VARCHAR(255), -- strPlayer team_id INTEGER, -- idTeam team_name VARCHAR(255), -- strTeam is_home VARCHAR(5), -- strHome ("Yes"/"No") position VARCHAR(50), -- strPosition position_short VARCHAR(5), -- strPositionShort (G/D/M/F) formation VARCHAR(20), -- strFormation squad_number INTEGER, -- intSquadNumber is_substitute VARCHAR(5) DEFAULT 'No', -- strSubstitute ("Yes"/"No") cutout_url VARCHAR(500), -- strCutout country VARCHAR(100), -- strCountry season VARCHAR(20), -- strSeason UNIQUE(event_id, player_id) ); CREATE TABLE IF NOT EXISTS event_timeline ( id INTEGER PRIMARY KEY, -- TheSportsDB idTimeline event_id INTEGER NOT NULL REFERENCES events(id) ON DELETE CASCADE, id_api_football VARCHAR(20), -- idAPIfootball event_name VARCHAR(255), -- strEvent event_type VARCHAR(100), -- strTimeline ("Goal", "subst", "Yellow Card") event_detail VARCHAR(255), -- strTimelineDetail ("Substitution 1") is_home VARCHAR(5), -- strHome ("Yes"/"No") minute INTEGER, -- intTime period VARCHAR(20), -- strPeriod player_id INTEGER, -- idPlayer player_name VARCHAR(255), -- strPlayer cutout_url VARCHAR(500), -- strCutout assist_id INTEGER, -- idAssist assist_name VARCHAR(255), -- strAssist team_id INTEGER, -- idTeam team_name VARCHAR(255), -- strTeam comment TEXT, -- strComment event_date DATE, -- dateEvent season VARCHAR(20), -- strSeason UNIQUE(event_id, minute, player_id, event_type) ); -- ══════════════════════════════════════════════════════════ -- Cache management -- ══════════════════════════════════════════════════════════ CREATE TABLE IF NOT EXISTS cache_meta ( cache_key VARCHAR(255) PRIMARY KEY, -- e.g. "standings:4346:2026" fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), ttl_seconds INTEGER NOT NULL DEFAULT 3600, data_json JSONB -- optional: store raw response ); -- ══════════════════════════════════════════════════════════ -- Indexes -- ══════════════════════════════════════════════════════════ CREATE INDEX IF NOT EXISTS idx_teams_league ON teams(league_id); CREATE INDEX IF NOT EXISTS idx_teams_followed ON teams(is_followed) WHERE is_followed; CREATE INDEX IF NOT EXISTS idx_players_team ON players(team_id); CREATE INDEX IF NOT EXISTS idx_events_date ON events(event_date); CREATE INDEX IF NOT EXISTS idx_events_home_team ON events(home_team_id); CREATE INDEX IF NOT EXISTS idx_events_away_team ON events(away_team_id); CREATE INDEX IF NOT EXISTS idx_events_league ON events(league_id); CREATE INDEX IF NOT EXISTS idx_events_status ON events(status); CREATE INDEX IF NOT EXISTS idx_event_stats_event ON event_stats(event_id); CREATE INDEX IF NOT EXISTS idx_event_lineup_event ON event_lineup(event_id); CREATE INDEX IF NOT EXISTS idx_event_tl_event ON event_timeline(event_id); CREATE INDEX IF NOT EXISTS idx_cache_meta_fetched ON cache_meta(fetched_at);