| 1 | DROP VIEW IF EXISTS vw_head_to_head CASCADE;
|
|---|
| 2 |
|
|---|
| 3 | DROP VIEW IF EXISTS vw_season_overview CASCADE;
|
|---|
| 4 |
|
|---|
| 5 | DROP VIEW IF EXISTS vw_utilization CASCADE;
|
|---|
| 6 |
|
|---|
| 7 | -- vw_head_to_head
|
|---|
| 8 | CREATE VIEW vw_head_to_head AS WITH match_goals AS (
|
|---|
| 9 | -- 1: count goals per (match, team)
|
|---|
| 10 | -- one row per team per match they scored in
|
|---|
| 11 | SELECT
|
|---|
| 12 | e.match_id,
|
|---|
| 13 | e.team_id,
|
|---|
| 14 | COUNT(*) AS goals
|
|---|
| 15 | FROM
|
|---|
| 16 | Event e
|
|---|
| 17 | JOIN Event_type et ON et.event_type_id = e.event_type_id -- left join to keep own goals because they don't have attributes
|
|---|
| 18 | LEFT JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 19 | AND ea.attribute_name = 'shot.outcome'
|
|---|
| 20 | WHERE
|
|---|
| 21 | (
|
|---|
| 22 | et.type = 'Shot'
|
|---|
| 23 | AND ea.attribute_value = 'Goal'
|
|---|
| 24 | )
|
|---|
| 25 | OR et.type = 'Own Goal For'
|
|---|
| 26 | GROUP BY
|
|---|
| 27 | e.match_id,
|
|---|
| 28 | e.team_id
|
|---|
| 29 | ),
|
|---|
| 30 | match_scores AS (
|
|---|
| 31 | -- 2: basically final score for the match
|
|---|
| 32 | SELECT
|
|---|
| 33 | m.match_id,
|
|---|
| 34 | m.match_date,
|
|---|
| 35 | m.home_team_id,
|
|---|
| 36 | m.away_team_id,
|
|---|
| 37 | -- coalesce to 0 because a team that didn't score has no row in match_goals
|
|---|
| 38 | COALESCE(hg.goals, 0) AS home_score,
|
|---|
| 39 | COALESCE(ag.goals, 0) AS away_score
|
|---|
| 40 | FROM
|
|---|
| 41 | Match m
|
|---|
| 42 | -- left joins to keep matches where one or both teams didn't score
|
|---|
| 43 | LEFT JOIN match_goals hg ON hg.match_id = m.match_id
|
|---|
| 44 | AND hg.team_id = m.home_team_id
|
|---|
| 45 | LEFT JOIN match_goals ag ON ag.match_id = m.match_id
|
|---|
| 46 | AND ag.team_id = m.away_team_id
|
|---|
| 47 | ),
|
|---|
| 48 | pairs AS (
|
|---|
| 49 | -- 3: re-orient every match into a canonical (team_a, team_b) pair where team_a_id < team_b_id
|
|---|
| 50 | -- home/away goal counts are flipped accordingly
|
|---|
| 51 | SELECT
|
|---|
| 52 | LEAST(home_team_id, away_team_id) AS team_a_id,
|
|---|
| 53 | GREATEST(home_team_id, away_team_id) AS team_b_id,
|
|---|
| 54 | match_date,
|
|---|
| 55 | CASE
|
|---|
| 56 | WHEN home_team_id < away_team_id THEN home_score
|
|---|
| 57 | ELSE away_score
|
|---|
| 58 | END AS team_a_goals,
|
|---|
| 59 | CASE
|
|---|
| 60 | WHEN home_team_id < away_team_id THEN away_score
|
|---|
| 61 | ELSE home_score
|
|---|
| 62 | END AS team_b_goals
|
|---|
| 63 | FROM
|
|---|
| 64 | match_scores
|
|---|
| 65 | ),
|
|---|
| 66 | ranked AS (
|
|---|
| 67 | -- 4: tag the most recent meeting in each pair with recency_rank = 1
|
|---|
| 68 | -- so the outer aggregate can pick out its score without a correlated subquery
|
|---|
| 69 | SELECT
|
|---|
| 70 | p.*,
|
|---|
| 71 | ROW_NUMBER() OVER (
|
|---|
| 72 | PARTITION BY team_a_id,
|
|---|
| 73 | team_b_id
|
|---|
| 74 | ORDER BY
|
|---|
| 75 | match_date DESC,
|
|---|
| 76 | team_a_goals DESC,
|
|---|
| 77 | team_b_goals DESC
|
|---|
| 78 | ) AS recency_rank
|
|---|
| 79 | FROM
|
|---|
| 80 | pairs p
|
|---|
| 81 | )
|
|---|
| 82 | -- 5: collapse to one row per pair with the full record
|
|---|
| 83 | SELECT
|
|---|
| 84 | r.team_a_id,
|
|---|
| 85 | ta.name AS team_a_name,
|
|---|
| 86 | r.team_b_id,
|
|---|
| 87 | tb.name AS team_b_name,
|
|---|
| 88 | COUNT(*) AS meetings,
|
|---|
| 89 | SUM(
|
|---|
| 90 | CASE
|
|---|
| 91 | WHEN r.team_a_goals > r.team_b_goals THEN 1
|
|---|
| 92 | ELSE 0
|
|---|
| 93 | END
|
|---|
| 94 | ) AS team_a_wins,
|
|---|
| 95 | SUM(
|
|---|
| 96 | CASE
|
|---|
| 97 | WHEN r.team_b_goals > r.team_a_goals THEN 1
|
|---|
| 98 | ELSE 0
|
|---|
| 99 | END
|
|---|
| 100 | ) AS team_b_wins,
|
|---|
| 101 | SUM(
|
|---|
| 102 | CASE
|
|---|
| 103 | WHEN r.team_a_goals = r.team_b_goals THEN 1
|
|---|
| 104 | ELSE 0
|
|---|
| 105 | END
|
|---|
| 106 | ) AS draws,
|
|---|
| 107 | SUM(r.team_a_goals + r.team_b_goals) AS total_goals,
|
|---|
| 108 | MAX(r.match_date) AS last_meeting_date,
|
|---|
| 109 | -- rows where recency_rank != 1 will have null values, so with max() we filter them out
|
|---|
| 110 | MAX(
|
|---|
| 111 | CASE
|
|---|
| 112 | WHEN r.recency_rank = 1 THEN r.team_a_goals
|
|---|
| 113 | END
|
|---|
| 114 | ) AS last_team_a_goals,
|
|---|
| 115 | MAX(
|
|---|
| 116 | CASE
|
|---|
| 117 | WHEN r.recency_rank = 1 THEN r.team_b_goals
|
|---|
| 118 | END
|
|---|
| 119 | ) AS last_team_b_goals
|
|---|
| 120 | FROM
|
|---|
| 121 | ranked r
|
|---|
| 122 | JOIN Team ta ON ta.team_id = r.team_a_id
|
|---|
| 123 | JOIN Team tb ON tb.team_id = r.team_b_id
|
|---|
| 124 | GROUP BY
|
|---|
| 125 | r.team_a_id,
|
|---|
| 126 | ta.name,
|
|---|
| 127 | r.team_b_id,
|
|---|
| 128 | tb.name;
|
|---|
| 129 |
|
|---|
| 130 | -- vw_season_overview
|
|---|
| 131 | -- One row per (division, season). Reuses the same Event-derived goal
|
|---|
| 132 | -- extractor as vw_head_to_head (CTEs can't be shared across views) to build
|
|---|
| 133 | -- per-match home/away/total/margin. Attendance per match is the count of
|
|---|
| 134 | -- scanned tickets; matches without ticket rows fall through a LEFT JOIN as
|
|---|
| 135 | -- NULL → 0 so averages stay comparable across seasons. Aggregation rolls up
|
|---|
| 136 | -- to the season for totals, per-game averages, the biggest goal margin in
|
|---|
| 137 | -- any single match, and the highest combined score in any single match.
|
|---|
| 138 | CREATE VIEW vw_season_overview AS WITH match_goals AS (
|
|---|
| 139 | -- 1: same goal-extraction pattern as vw_head_to_head
|
|---|
| 140 | SELECT
|
|---|
| 141 | e.match_id,
|
|---|
| 142 | e.team_id,
|
|---|
| 143 | COUNT(*) AS goals
|
|---|
| 144 | FROM
|
|---|
| 145 | Event e
|
|---|
| 146 | JOIN Event_type et ON et.event_type_id = e.event_type_id
|
|---|
| 147 | LEFT JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 148 | AND ea.attribute_name = 'shot.outcome'
|
|---|
| 149 | WHERE
|
|---|
| 150 | (
|
|---|
| 151 | et.type = 'Shot'
|
|---|
| 152 | AND ea.attribute_value = 'Goal'
|
|---|
| 153 | )
|
|---|
| 154 | OR et.type = 'Own Goal For'
|
|---|
| 155 | GROUP BY
|
|---|
| 156 | e.match_id,
|
|---|
| 157 | e.team_id
|
|---|
| 158 | ),
|
|---|
| 159 | match_summary AS (
|
|---|
| 160 | -- 2: one row per match with derived final result and other metrics
|
|---|
| 161 | SELECT
|
|---|
| 162 | m.match_id,
|
|---|
| 163 | m.season_id,
|
|---|
| 164 | COALESCE(hg.goals, 0) AS home_score,
|
|---|
| 165 | COALESCE(ag.goals, 0) AS away_score,
|
|---|
| 166 | COALESCE(hg.goals, 0) + COALESCE(ag.goals, 0) AS total_goals,
|
|---|
| 167 | ABS(COALESCE(hg.goals, 0) - COALESCE(ag.goals, 0)) AS goal_margin
|
|---|
| 168 | FROM
|
|---|
| 169 | Match m
|
|---|
| 170 | LEFT JOIN match_goals hg ON hg.match_id = m.match_id
|
|---|
| 171 | AND hg.team_id = m.home_team_id
|
|---|
| 172 | LEFT JOIN match_goals ag ON ag.match_id = m.match_id
|
|---|
| 173 | AND ag.team_id = m.away_team_id
|
|---|
| 174 | ),
|
|---|
| 175 | match_attendance AS (
|
|---|
| 176 | -- 3: tickets actually scanned at the gate
|
|---|
| 177 | -- null becomes 0 via the left join + coalesce in the outer query
|
|---|
| 178 | SELECT
|
|---|
| 179 | match_id,
|
|---|
| 180 | COUNT(*) AS attendance
|
|---|
| 181 | FROM
|
|---|
| 182 | Ticket
|
|---|
| 183 | WHERE
|
|---|
| 184 | is_scanned = true
|
|---|
| 185 | GROUP BY
|
|---|
| 186 | match_id
|
|---|
| 187 | )
|
|---|
| 188 | SELECT
|
|---|
| 189 | s.season_id,
|
|---|
| 190 | s.season_name,
|
|---|
| 191 | d.division_id,
|
|---|
| 192 | d.name AS division_name,
|
|---|
| 193 | d.country AS division_country,
|
|---|
| 194 | COUNT(ms.match_id) AS total_matches,
|
|---|
| 195 | SUM(ms.total_goals) AS total_goals,
|
|---|
| 196 | ROUND(AVG(ms.total_goals) :: numeric, 2) AS avg_goals_per_game,
|
|---|
| 197 | -- largest goal margin in any single match this season
|
|---|
| 198 | MAX(ms.goal_margin) AS biggest_win_margin,
|
|---|
| 199 | -- max combined goals
|
|---|
| 200 | MAX(ms.total_goals) AS highest_scoring_match_goals,
|
|---|
| 201 | ROUND(AVG(COALESCE(ma.attendance, 0)) :: numeric, 0) AS avg_attendance
|
|---|
| 202 | FROM
|
|---|
| 203 | Season s
|
|---|
| 204 | JOIN Division d ON d.division_id = s.division_id
|
|---|
| 205 | LEFT JOIN match_summary ms ON ms.season_id = s.season_id
|
|---|
| 206 | LEFT JOIN match_attendance ma ON ma.match_id = ms.match_id
|
|---|
| 207 | GROUP BY
|
|---|
| 208 | s.season_id,
|
|---|
| 209 | s.season_name,
|
|---|
| 210 | d.division_id,
|
|---|
| 211 | d.name,
|
|---|
| 212 | d.country;
|
|---|
| 213 |
|
|---|
| 214 | -- vw_utilization
|
|---|
| 215 | CREATE VIEW vw_utilization AS WITH stadium_season_matches AS (
|
|---|
| 216 | -- 1: count matches hosted per (stadium, season)
|
|---|
| 217 | SELECT
|
|---|
| 218 | m.stadium_id,
|
|---|
| 219 | m.season_id,
|
|---|
| 220 | COUNT(*) AS matches_hosted
|
|---|
| 221 | FROM
|
|---|
| 222 | Match m
|
|---|
| 223 | GROUP BY
|
|---|
| 224 | m.stadium_id,
|
|---|
| 225 | m.season_id
|
|---|
| 226 | ),
|
|---|
| 227 | ticket_stats AS (
|
|---|
| 228 | -- 2: per (stadium, season) ticket aggregates.
|
|---|
| 229 | -- we join Ticket with Match to map tickets back to a stadium and season
|
|---|
| 230 | -- left join keeps stadiums/seasons with zero tickets visible
|
|---|
| 231 | SELECT
|
|---|
| 232 | m.stadium_id,
|
|---|
| 233 | m.season_id,
|
|---|
| 234 | COUNT(t.ticket_id) AS tickets_issued,
|
|---|
| 235 | COUNT(t.ticket_id) FILTER (
|
|---|
| 236 | WHERE
|
|---|
| 237 | t.is_scanned
|
|---|
| 238 | ) AS tickets_scanned,
|
|---|
| 239 | AVG(t.price) AS avg_ticket_price,
|
|---|
| 240 | SUM(t.price) AS total_revenue
|
|---|
| 241 | FROM
|
|---|
| 242 | Match m
|
|---|
| 243 | LEFT JOIN Ticket t ON t.match_id = m.match_id
|
|---|
| 244 | GROUP BY
|
|---|
| 245 | m.stadium_id,
|
|---|
| 246 | m.season_id
|
|---|
| 247 | )
|
|---|
| 248 | SELECT
|
|---|
| 249 | st.stadium_id,
|
|---|
| 250 | st.name AS stadium_name,
|
|---|
| 251 | st.country,
|
|---|
| 252 | st.capacity,
|
|---|
| 253 | se.season_id,
|
|---|
| 254 | se.season_name,
|
|---|
| 255 | d.name AS division_name,
|
|---|
| 256 | ssm.matches_hosted,
|
|---|
| 257 | st.capacity * ssm.matches_hosted AS total_seats_available,
|
|---|
| 258 | COALESCE(ts.tickets_issued, 0) AS tickets_issued,
|
|---|
| 259 | COALESCE(ts.tickets_scanned, 0) AS tickets_scanned,
|
|---|
| 260 | -- null when no tickets were issued, otherwise the divide is safe
|
|---|
| 261 | CASE
|
|---|
| 262 | WHEN COALESCE(ts.tickets_issued, 0) = 0 THEN NULL
|
|---|
| 263 | ELSE ROUND(
|
|---|
| 264 | (ts.tickets_scanned :: numeric / ts.tickets_issued) * 100,
|
|---|
| 265 | 2
|
|---|
| 266 | )
|
|---|
| 267 | END AS scan_rate_pct,
|
|---|
| 268 | CASE
|
|---|
| 269 | WHEN st.capacity * ssm.matches_hosted = 0 THEN NULL
|
|---|
| 270 | ELSE ROUND(
|
|---|
| 271 | (
|
|---|
| 272 | COALESCE(ts.tickets_issued, 0) :: numeric / (st.capacity * ssm.matches_hosted)
|
|---|
| 273 | ) * 100,
|
|---|
| 274 | 2
|
|---|
| 275 | )
|
|---|
| 276 | END AS utilization_pct,
|
|---|
| 277 | ROUND(COALESCE(ts.avg_ticket_price, 0) :: numeric, 2) AS avg_ticket_price,
|
|---|
| 278 | COALESCE(ts.total_revenue, 0) AS total_revenue
|
|---|
| 279 | FROM
|
|---|
| 280 | Stadium st -- inner join only shows stadiums that actually hosted matches in a season
|
|---|
| 281 | JOIN stadium_season_matches ssm ON ssm.stadium_id = st.stadium_id
|
|---|
| 282 | JOIN Season se ON se.season_id = ssm.season_id
|
|---|
| 283 | JOIN Division d ON d.division_id = se.division_id
|
|---|
| 284 | LEFT JOIN ticket_stats ts ON ts.stadium_id = st.stadium_id
|
|---|
| 285 | AND ts.season_id = ssm.season_id;
|
|---|
| 286 |
|
|---|
| 287 |
|
|---|
| 288 | CREATE
|
|---|
| 289 | OR REPLACE VIEW vw_match_summary AS WITH goals AS (
|
|---|
| 290 | -- One row per goal scored, tagged with which side (home/away) scored it.
|
|---|
| 291 | SELECT
|
|---|
| 292 | e.match_id,
|
|---|
| 293 | e.team_id AS scoring_team_id
|
|---|
| 294 | FROM
|
|---|
| 295 | Event e
|
|---|
| 296 | JOIN Event_type et ON et.event_type_id = e.event_type_id
|
|---|
| 297 | JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 298 | WHERE
|
|---|
| 299 | et.type = 'Shot'
|
|---|
| 300 | AND ea.attribute_name = 'shot.outcome'
|
|---|
| 301 | AND ea.attribute_value = 'Goal'
|
|---|
| 302 | ),
|
|---|
| 303 | goal_counts AS (
|
|---|
| 304 | SELECT
|
|---|
| 305 | m.match_id,
|
|---|
| 306 | COUNT(*) FILTER (
|
|---|
| 307 | WHERE
|
|---|
| 308 | g.scoring_team_id = m.home_team_id
|
|---|
| 309 | ) AS home_goals,
|
|---|
| 310 | COUNT(*) FILTER (
|
|---|
| 311 | WHERE
|
|---|
| 312 | g.scoring_team_id = m.away_team_id
|
|---|
| 313 | ) AS away_goals
|
|---|
| 314 | FROM
|
|---|
| 315 | "match" m
|
|---|
| 316 | LEFT JOIN goals g ON g.match_id = m.match_id
|
|---|
| 317 | GROUP BY
|
|---|
| 318 | m.match_id
|
|---|
| 319 | ),
|
|---|
| 320 | cards AS (
|
|---|
| 321 | -- Counts both 'foul_committed.card' and 'bad_behaviour.card' attributes.
|
|---|
| 322 | SELECT
|
|---|
| 323 | e.match_id,
|
|---|
| 324 | COUNT(*) FILTER (
|
|---|
| 325 | WHERE
|
|---|
| 326 | ea.attribute_value IN ('Yellow Card', 'Second Yellow')
|
|---|
| 327 | ) AS yellow_cards,
|
|---|
| 328 | COUNT(*) FILTER (
|
|---|
| 329 | WHERE
|
|---|
| 330 | ea.attribute_value = 'Red Card'
|
|---|
| 331 | ) AS red_cards,
|
|---|
| 332 | COUNT(*) AS total_cards
|
|---|
| 333 | FROM
|
|---|
| 334 | Event e
|
|---|
| 335 | JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 336 | WHERE
|
|---|
| 337 | ea.attribute_name IN ('foul_committed.card', 'bad_behaviour.card')
|
|---|
| 338 | GROUP BY
|
|---|
| 339 | e.match_id
|
|---|
| 340 | ),
|
|---|
| 341 | main_ref AS (
|
|---|
| 342 | -- The "main" referee per match. role='main' if available, else any.
|
|---|
| 343 | SELECT
|
|---|
| 344 | DISTINCT ON (rm.match_id) rm.match_id,
|
|---|
| 345 | rm.referee_id
|
|---|
| 346 | FROM
|
|---|
| 347 | Referee_match rm
|
|---|
| 348 | ORDER BY
|
|---|
| 349 | rm.match_id,
|
|---|
| 350 | (rm.role = 'main') DESC,
|
|---|
| 351 | rm.referee_match_id
|
|---|
| 352 | )
|
|---|
| 353 | SELECT
|
|---|
| 354 | m.match_id,
|
|---|
| 355 | m.match_date,
|
|---|
| 356 | s.season_id,
|
|---|
| 357 | s.season_name,
|
|---|
| 358 | d.division_id,
|
|---|
| 359 | d.name AS division_name,
|
|---|
| 360 | d.country AS division_country,
|
|---|
| 361 | ht.team_id AS home_team_id,
|
|---|
| 362 | ht.name AS home_team_name,
|
|---|
| 363 | at.team_id AS away_team_id,
|
|---|
| 364 | at.name AS away_team_name,
|
|---|
| 365 | st.stadium_id,
|
|---|
| 366 | st.name AS stadium_name,
|
|---|
| 367 | st.country AS stadium_country,
|
|---|
| 368 | r.referee_id,
|
|---|
| 369 | r.name AS referee_name,
|
|---|
| 370 | COALESCE(gc.home_goals, 0) AS home_goals,
|
|---|
| 371 | COALESCE(gc.away_goals, 0) AS away_goals,
|
|---|
| 372 | CASE
|
|---|
| 373 | WHEN COALESCE(gc.home_goals, 0) > COALESCE(gc.away_goals, 0) THEN 'H'
|
|---|
| 374 | WHEN COALESCE(gc.home_goals, 0) < COALESCE(gc.away_goals, 0) THEN 'A'
|
|---|
| 375 | ELSE 'D'
|
|---|
| 376 | END AS result,
|
|---|
| 377 | COALESCE(c.yellow_cards, 0) AS yellow_cards,
|
|---|
| 378 | COALESCE(c.red_cards, 0) AS red_cards,
|
|---|
| 379 | COALESCE(c.total_cards, 0) AS total_cards
|
|---|
| 380 | FROM
|
|---|
| 381 | "match" m
|
|---|
| 382 | JOIN Season s ON s.season_id = m.season_id
|
|---|
| 383 | JOIN Division d ON d.division_id = s.division_id
|
|---|
| 384 | JOIN Team ht ON ht.team_id = m.home_team_id
|
|---|
| 385 | JOIN Team at ON at.team_id = m.away_team_id
|
|---|
| 386 | JOIN Stadium st ON st.stadium_id = m.stadium_id
|
|---|
| 387 | LEFT JOIN goal_counts gc ON gc.match_id = m.match_id
|
|---|
| 388 | LEFT JOIN cards c ON c.match_id = m.match_id
|
|---|
| 389 | LEFT JOIN main_ref mr ON mr.match_id = m.match_id
|
|---|
| 390 | LEFT JOIN Referee r ON r.referee_id = mr.referee_id;
|
|---|
| 391 |
|
|---|
| 392 |
|
|---|
| 393 |
|
|---|
| 394 | CREATE
|
|---|
| 395 | OR REPLACE VIEW vw_team_season_table AS WITH match_results AS (
|
|---|
| 396 | -- Reuse goal counts from vw_match_summary for consistency.
|
|---|
| 397 | SELECT
|
|---|
| 398 | match_id,
|
|---|
| 399 | season_id,
|
|---|
| 400 | home_team_id,
|
|---|
| 401 | away_team_id,
|
|---|
| 402 | home_goals,
|
|---|
| 403 | away_goals
|
|---|
| 404 | FROM
|
|---|
| 405 | vw_match_summary
|
|---|
| 406 | ),
|
|---|
| 407 | team_match_rows AS (
|
|---|
| 408 | -- One row per (team, match) — UNION home and away perspectives.
|
|---|
| 409 | SELECT
|
|---|
| 410 | season_id,
|
|---|
| 411 | home_team_id AS team_id,
|
|---|
| 412 | home_goals AS goals_for,
|
|---|
| 413 | away_goals AS goals_against,
|
|---|
| 414 | CASE
|
|---|
| 415 | WHEN home_goals > away_goals THEN 'W'
|
|---|
| 416 | WHEN home_goals < away_goals THEN 'L'
|
|---|
| 417 | ELSE 'D'
|
|---|
| 418 | END AS result_letter
|
|---|
| 419 | FROM
|
|---|
| 420 | match_results
|
|---|
| 421 | UNION
|
|---|
| 422 | ALL
|
|---|
| 423 | SELECT
|
|---|
| 424 | season_id,
|
|---|
| 425 | away_team_id AS team_id,
|
|---|
| 426 | away_goals AS goals_for,
|
|---|
| 427 | home_goals AS goals_against,
|
|---|
| 428 | CASE
|
|---|
| 429 | WHEN away_goals > home_goals THEN 'W'
|
|---|
| 430 | WHEN away_goals < home_goals THEN 'L'
|
|---|
| 431 | ELSE 'D'
|
|---|
| 432 | END AS result_letter
|
|---|
| 433 | FROM
|
|---|
| 434 | match_results
|
|---|
| 435 | )
|
|---|
| 436 | SELECT
|
|---|
| 437 | s.season_id,
|
|---|
| 438 | s.season_name,
|
|---|
| 439 | d.division_id,
|
|---|
| 440 | d.name AS division_name,
|
|---|
| 441 | t.team_id,
|
|---|
| 442 | t.name AS team_name,
|
|---|
| 443 | COUNT(*) AS matches_played,
|
|---|
| 444 | COUNT(*) FILTER (
|
|---|
| 445 | WHERE
|
|---|
| 446 | tmr.result_letter = 'W'
|
|---|
| 447 | ) AS wins,
|
|---|
| 448 | COUNT(*) FILTER (
|
|---|
| 449 | WHERE
|
|---|
| 450 | tmr.result_letter = 'D'
|
|---|
| 451 | ) AS draws,
|
|---|
| 452 | COUNT(*) FILTER (
|
|---|
| 453 | WHERE
|
|---|
| 454 | tmr.result_letter = 'L'
|
|---|
| 455 | ) AS losses,
|
|---|
| 456 | COALESCE(SUM(tmr.goals_for), 0) AS goals_for,
|
|---|
| 457 | COALESCE(SUM(tmr.goals_against), 0) AS goals_against,
|
|---|
| 458 | COALESCE(SUM(tmr.goals_for) - SUM(tmr.goals_against), 0) AS goal_difference,
|
|---|
| 459 | 3 * COUNT(*) FILTER (
|
|---|
| 460 | WHERE
|
|---|
| 461 | tmr.result_letter = 'W'
|
|---|
| 462 | ) + 1 * COUNT(*) FILTER (
|
|---|
| 463 | WHERE
|
|---|
| 464 | tmr.result_letter = 'D'
|
|---|
| 465 | ) AS points
|
|---|
| 466 | FROM
|
|---|
| 467 | team_match_rows tmr
|
|---|
| 468 | JOIN Season s ON s.season_id = tmr.season_id
|
|---|
| 469 | JOIN Division d ON d.division_id = s.division_id
|
|---|
| 470 | JOIN Team t ON t.team_id = tmr.team_id
|
|---|
| 471 | GROUP BY
|
|---|
| 472 | s.season_id,
|
|---|
| 473 | s.season_name,
|
|---|
| 474 | d.division_id,
|
|---|
| 475 | d.name,
|
|---|
| 476 | t.team_id,
|
|---|
| 477 | t.name;
|
|---|
| 478 |
|
|---|
| 479 |
|
|---|
| 480 | CREATE
|
|---|
| 481 | OR REPLACE VIEW vw_player_season_stats AS WITH lineup_per_player_season AS (
|
|---|
| 482 | -- The team a player primarily played for in this season (most appearances).
|
|---|
| 483 | SELECT
|
|---|
| 484 | l.player_id,
|
|---|
| 485 | m.season_id,
|
|---|
| 486 | l.team_id,
|
|---|
| 487 | COUNT(*) AS matches_played,
|
|---|
| 488 | COUNT(*) FILTER (
|
|---|
| 489 | WHERE
|
|---|
| 490 | l.is_starter
|
|---|
| 491 | ) AS starts,
|
|---|
| 492 | COUNT(*) FILTER (
|
|---|
| 493 | WHERE
|
|---|
| 494 | NOT l.is_starter
|
|---|
| 495 | ) AS sub_apps,
|
|---|
| 496 | ROW_NUMBER() OVER (
|
|---|
| 497 | PARTITION BY l.player_id,
|
|---|
| 498 | m.season_id
|
|---|
| 499 | ORDER BY
|
|---|
| 500 | COUNT(*) DESC
|
|---|
| 501 | ) AS rn
|
|---|
| 502 | FROM
|
|---|
| 503 | Lineup l
|
|---|
| 504 | JOIN "match" m ON m.match_id = l.match_id
|
|---|
| 505 | GROUP BY
|
|---|
| 506 | l.player_id,
|
|---|
| 507 | m.season_id,
|
|---|
| 508 | l.team_id
|
|---|
| 509 | ),
|
|---|
| 510 | primary_team AS (
|
|---|
| 511 | SELECT
|
|---|
| 512 | player_id,
|
|---|
| 513 | season_id,
|
|---|
| 514 | team_id,
|
|---|
| 515 | matches_played,
|
|---|
| 516 | starts,
|
|---|
| 517 | sub_apps
|
|---|
| 518 | FROM
|
|---|
| 519 | lineup_per_player_season
|
|---|
| 520 | WHERE
|
|---|
| 521 | rn = 1
|
|---|
| 522 | ),
|
|---|
| 523 | goals AS (
|
|---|
| 524 | SELECT
|
|---|
| 525 | e.player_id,
|
|---|
| 526 | m.season_id,
|
|---|
| 527 | COUNT(*) AS goals
|
|---|
| 528 | FROM
|
|---|
| 529 | Event e
|
|---|
| 530 | JOIN Event_type et ON et.event_type_id = e.event_type_id
|
|---|
| 531 | JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 532 | JOIN "match" m ON m.match_id = e.match_id
|
|---|
| 533 | WHERE
|
|---|
| 534 | et.type = 'Shot'
|
|---|
| 535 | AND ea.attribute_name = 'shot.outcome'
|
|---|
| 536 | AND ea.attribute_value = 'Goal'
|
|---|
| 537 | GROUP BY
|
|---|
| 538 | e.player_id,
|
|---|
| 539 | m.season_id
|
|---|
| 540 | ),
|
|---|
| 541 | assists AS (
|
|---|
| 542 | -- A "goal assist" in StatsBomb is a Pass with pass.goal_assist = 'True'.
|
|---|
| 543 | SELECT
|
|---|
| 544 | e.player_id,
|
|---|
| 545 | m.season_id,
|
|---|
| 546 | COUNT(*) AS assists
|
|---|
| 547 | FROM
|
|---|
| 548 | Event e
|
|---|
| 549 | JOIN Event_type et ON et.event_type_id = e.event_type_id
|
|---|
| 550 | JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 551 | JOIN "match" m ON m.match_id = e.match_id
|
|---|
| 552 | WHERE
|
|---|
| 553 | et.type = 'Pass'
|
|---|
| 554 | AND ea.attribute_name = 'pass.goal_assist'
|
|---|
| 555 | AND ea.attribute_value IN ('True', 'true')
|
|---|
| 556 | GROUP BY
|
|---|
| 557 | e.player_id,
|
|---|
| 558 | m.season_id
|
|---|
| 559 | ),
|
|---|
| 560 | yellow_cards AS (
|
|---|
| 561 | SELECT
|
|---|
| 562 | e.player_id,
|
|---|
| 563 | m.season_id,
|
|---|
| 564 | COUNT(*) AS yellow_cards
|
|---|
| 565 | FROM
|
|---|
| 566 | Event e
|
|---|
| 567 | JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 568 | JOIN "match" m ON m.match_id = e.match_id
|
|---|
| 569 | WHERE
|
|---|
| 570 | ea.attribute_name IN ('foul_committed.card', 'bad_behaviour.card')
|
|---|
| 571 | AND ea.attribute_value IN ('Yellow Card', 'Second Yellow')
|
|---|
| 572 | GROUP BY
|
|---|
| 573 | e.player_id,
|
|---|
| 574 | m.season_id
|
|---|
| 575 | ),
|
|---|
| 576 | red_cards AS (
|
|---|
| 577 | SELECT
|
|---|
| 578 | e.player_id,
|
|---|
| 579 | m.season_id,
|
|---|
| 580 | COUNT(*) AS red_cards
|
|---|
| 581 | FROM
|
|---|
| 582 | Event e
|
|---|
| 583 | JOIN Event_attribute ea ON ea.event_id = e.event_id
|
|---|
| 584 | JOIN "match" m ON m.match_id = e.match_id
|
|---|
| 585 | WHERE
|
|---|
| 586 | ea.attribute_name IN ('foul_committed.card', 'bad_behaviour.card')
|
|---|
| 587 | AND ea.attribute_value = 'Red Card'
|
|---|
| 588 | GROUP BY
|
|---|
| 589 | e.player_id,
|
|---|
| 590 | m.season_id
|
|---|
| 591 | )
|
|---|
| 592 | SELECT
|
|---|
| 593 | p.player_id,
|
|---|
| 594 | p.name AS player_name,
|
|---|
| 595 | pt.season_id,
|
|---|
| 596 | s.season_name,
|
|---|
| 597 | pt.team_id,
|
|---|
| 598 | t.name AS team_name,
|
|---|
| 599 | pt.matches_played,
|
|---|
| 600 | pt.starts,
|
|---|
| 601 | pt.sub_apps,
|
|---|
| 602 | -- Minutes proxy: starters get 90, subs get 30
|
|---|
| 603 | (pt.starts * 90 + pt.sub_apps * 30) AS minutes_played_estimate,
|
|---|
| 604 | COALESCE(g.goals, 0) AS goals,
|
|---|
| 605 | COALESCE(a.assists, 0) AS assists,
|
|---|
| 606 | COALESCE(yc.yellow_cards, 0) AS yellow_cards,
|
|---|
| 607 | COALESCE(rc.red_cards, 0) AS red_cards
|
|---|
| 608 | FROM
|
|---|
| 609 | primary_team pt
|
|---|
| 610 | JOIN Player p ON p.player_id = pt.player_id
|
|---|
| 611 | JOIN Season s ON s.season_id = pt.season_id
|
|---|
| 612 | JOIN Team t ON t.team_id = pt.team_id
|
|---|
| 613 | LEFT JOIN goals g ON g.player_id = pt.player_id
|
|---|
| 614 | AND g.season_id = pt.season_id
|
|---|
| 615 | LEFT JOIN assists a ON a.player_id = pt.player_id
|
|---|
| 616 | AND a.season_id = pt.season_id
|
|---|
| 617 | LEFT JOIN yellow_cards yc ON yc.player_id = pt.player_id
|
|---|
| 618 | AND yc.season_id = pt.season_id
|
|---|
| 619 | LEFT JOIN red_cards rc ON rc.player_id = pt.player_id
|
|---|
| 620 | AND rc.season_id = pt.season_id; |
|---|