DatabaseCreation: views.sql

File views.sql, 17.6 KB (added by 231091, 5 hours ago)
Line 
1DROP VIEW IF EXISTS vw_head_to_head CASCADE;
2
3DROP VIEW IF EXISTS vw_season_overview CASCADE;
4
5DROP VIEW IF EXISTS vw_utilization CASCADE;
6
7-- vw_head_to_head
8CREATE 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),
30match_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),
48pairs 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),
66ranked 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
83SELECT
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
120FROM
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
124GROUP 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.
138CREATE 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),
159match_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),
175match_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)
188SELECT
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
202FROM
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
207GROUP BY
208 s.season_id,
209 s.season_name,
210 d.division_id,
211 d.name,
212 d.country;
213
214-- vw_utilization
215CREATE 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),
227ticket_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)
248SELECT
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
279FROM
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
288CREATE
289OR 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),
303goal_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),
320cards 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),
341main_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)
353SELECT
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
380FROM
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
394CREATE
395OR 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),
407team_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)
436SELECT
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
466FROM
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
471GROUP 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
480CREATE
481OR 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),
510primary_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),
523goals 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),
541assists 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),
560yellow_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),
576red_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)
592SELECT
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
608FROM
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;