Files
nba-fan-hub/db.py
T

215 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
数据库层:SQLite 统一存储结构化数据。
Schema 采用"运动-联赛-球队-球员-比赛"分层设计,天然支持扩展:
- 新增 CBA → sports 表加一行 basketballleagues 表加 CBA(复用全部代码)
- 新增足球 → sports 表加 footballleague 可加 英超/西甲,球员字段可扩展
- 新增人物角色 → persons.role 自由扩展(coach/agent/commentator/host/gm...
"""
import sqlite3
import threading
from config import DB_PATH
_SCHEMA = """
CREATE TABLE IF NOT EXISTS sports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL, -- 'basketball' / 'football' ...
name TEXT NOT NULL, -- '篮球' / '足球'
name_en TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS leagues (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sport_id INTEGER NOT NULL REFERENCES sports(id),
code TEXT UNIQUE NOT NULL, -- 'NBA' / 'CBA' / 'EPL'
name TEXT NOT NULL, -- '美国职业篮球联赛'
name_en TEXT,
country TEXT,
season TEXT, -- '2025-26'
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS teams (
id INTEGER PRIMARY KEY AUTOINCREMENT,
league_id INTEGER NOT NULL REFERENCES leagues(id),
code TEXT, -- 'LAL'
name TEXT NOT NULL, -- '洛杉矶湖人'
name_en TEXT, -- 'Los Angeles Lakers'
city TEXT,
arena TEXT,
founded INTEGER,
champion_count INTEGER,
head_coach TEXT,
logo_url TEXT,
intro TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS players (
id INTEGER PRIMARY KEY AUTOINCREMENT,
team_id INTEGER REFERENCES teams(id),
name TEXT NOT NULL,
name_en TEXT,
position TEXT, -- PG/SG/SF/PF/C
number INTEGER,
height_cm INTEGER,
weight_kg INTEGER,
birth_date TEXT,
country TEXT,
draft_year INTEGER,
draft_pick INTEGER, -- 0 = 落选
salary_m REAL, -- 年薪(万美元)
season_pts REAL, season_reb REAL, season_ast REAL,
season_stl REAL, season_blk REAL, season_min REAL, -- 本赛季场均
career_pts REAL, career_reb REAL, career_ast REAL, -- 生涯场均
career_games INTEGER,
career_steals INTEGER, career_blocks INTEGER, -- 生涯总数
awards TEXT, -- 主要荣誉
bio TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS games (
id INTEGER PRIMARY KEY AUTOINCREMENT,
league_id INTEGER NOT NULL REFERENCES leagues(id),
season TEXT,
round_name TEXT, -- 常规赛/季后赛首轮/总决赛...
game_time TEXT, -- '2026-06-05 09:00'
status TEXT, -- scheduled / finished / live
home_team_id INTEGER REFERENCES teams(id),
away_team_id INTEGER REFERENCES teams(id),
home_score INTEGER,
away_score INTEGER,
venue TEXT,
broadcast TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS game_player_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
game_id INTEGER NOT NULL REFERENCES games(id),
player_id INTEGER NOT NULL REFERENCES players(id),
team_id INTEGER NOT NULL REFERENCES teams(id),
points INTEGER DEFAULT 0, rebounds INTEGER DEFAULT 0,
assists INTEGER DEFAULT 0, steals INTEGER DEFAULT 0,
blocks INTEGER DEFAULT 0, minutes INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS standings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
league_id INTEGER NOT NULL REFERENCES leagues(id),
season TEXT,
team_id INTEGER NOT NULL REFERENCES teams(id),
wins INTEGER DEFAULT 0, losses INTEGER DEFAULT 0,
win_pct REAL DEFAULT 0,
conference TEXT, -- 西部/东部
rank INTEGER
);
CREATE TABLE IF NOT EXISTS news (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sport_id INTEGER NOT NULL REFERENCES sports(id),
league_id INTEGER REFERENCES leagues(id),
team_id INTEGER REFERENCES teams(id),
title TEXT NOT NULL,
content TEXT NOT NULL,
author TEXT,
source TEXT,
publish_time TEXT,
tags TEXT,
kind TEXT DEFAULT 'news', -- news / wiki(百科词条)
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS persons (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sport_id INTEGER NOT NULL REFERENCES sports(id),
league_id INTEGER REFERENCES leagues(id),
team_id INTEGER REFERENCES teams(id),
name TEXT NOT NULL,
name_en TEXT,
role TEXT, -- coach/agent/commentator/host/gm/legend...
role_cn TEXT, -- 教练/经纪人/评论员/主持人/总经理...
title TEXT,
bio TEXT,
achievements TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS site_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE INDEX IF NOT EXISTS idx_players_team ON players(team_id);
CREATE INDEX IF NOT EXISTS idx_players_name ON players(name);
CREATE INDEX IF NOT EXISTS idx_games_time ON games(game_time);
CREATE INDEX IF NOT EXISTS idx_games_team ON games(home_team_id);
CREATE INDEX IF NOT EXISTS idx_news_time ON news(publish_time);
CREATE INDEX IF NOT EXISTS idx_persons_role ON persons(role);
"""
_lock = threading.Lock()
def get_conn():
conn = sqlite3.connect(DB_PATH, timeout=30)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
return conn
def init_db():
with _lock:
conn = get_conn()
try:
conn.executescript(_SCHEMA)
conn.commit()
finally:
conn.close()
def query(sql, args=()):
conn = get_conn()
try:
cur = conn.execute(sql, args)
return [dict(r) for r in cur.fetchall()]
finally:
conn.close()
def query_one(sql, args=()):
rows = query(sql, args)
return rows[0] if rows else None
def execute(sql, args=()):
conn = get_conn()
try:
cur = conn.execute(sql, args)
conn.commit()
return cur.lastrowid
finally:
conn.close()
def executemany(sql, rows):
conn = get_conn()
try:
conn.executemany(sql, rows)
conn.commit()
finally:
conn.close()
def table_count(table):
return query_one(f"SELECT COUNT(*) AS c FROM {table}")["c"]
def fuzzy(expr): # SQL LIKE 模糊匹配(转义 % _
return expr.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")