Back to feed

Project docs

Ski Jumping Statistics

PostgreSQL analysis of men's Ski Jumping World Cup results, supported by a Python data collection pipeline.

Round Results

A ranking of every jumper in a single competition round, based only on the points scored for that round’s jump.

Rules

  • Each round is ranked independently using the points awarded for one jump.
  • A World Cup competition usually consists of two rounds.
  • Every jump is assigned a position within its competition and round.

Ranking each round

To support further analysis, a round_results view is created. It contains the same number of rows as the results table, but keeps only the essential scoring data (total_points) and adds a pos column with the ranking for each round.

create view round_results
as
select
	r.race_id,
	r.jumper_id,
	r.total_points::decimal(10, 2),
	r.round_no,
	rank() over (
		partition by r.race_id, r.round_no 
		order by total_points desc
	) as pos
from results r;

View presentation

First 20 raw round_results view20 rows
race_idjumper_idtotal_pointsround_nopos
4916122148.2011
491676147.0012
491668146.3013
491673146.1014
491672146.0015
491681145.5016
491664142.3017
491670142.3018
491674140.4019
491680138.70110
491679138.20111
491675138.00112
491667137.80113
491690135.40114
491684134.20115
491678133.30116
491697130.00117
491682129.70118
491691129.60119
4916186129.20120

As shown above, the view is still intentionally close to the raw data. Finding a specific round is not very intuitive because it requires knowing the exact race_id or competition date.

Finding a competition

From a fan’s perspective, competitions are usually identified by their year, location, and order—for example, the second event held in Engelberg in 2017. The find_race_id function accepts those three values and returns the corresponding race_id.

create function find_race_id(year int, city varchar(100), race_no int)
returns competitions.race_id%type
language plpgsql
as $$
declare
	found_id competitions.race_id%type = -1;
	i int = 1;
begin
	for found_id in (
		select race_id
		from competitions
		where 
			competition_started >= make_date(year, 1, 1)
			and competition_started <= make_date(year, 12, 31)
			and hill_city = city
	)
	loop
		if i = race_no then
			return found_id;
		else
			i = i + 1;
		end if;
	end loop;

	if not found then
		raise warning 'Such competition was not found';
	else
		raise warning 'Similar competitions exists but the race_no = % is too high', race_no;
	end if;

	return -1;
end $$;

Function presentation

select
	'First competition held in Kulm in 2026' as wanted,
	find_race_id(2026, 'Kulm', 1) as race_id
union all
select
	'Second competition held in Oberstdorf in 2025' as wanted,
	find_race_id(2025, 'Oberstdorf', 2) as race_id
union all
select
	'Second competition held in Zakopane in 2020' as wanted,
	find_race_id(2020, 'Zakopane', 2) as race_id;
Competition identifiers returned by PostgreSQL3 rows
wantedrace_id
First competition held in Kulm in 20267553
Second competition held in Oberstdorf in 20257214
Second competition held in Zakopane in 2020-1

The lookup for the second Zakopane competition in 2020 returns -1 because only one competition was held there that year.

Presenting a round

Finally, the view and function can be combined to present a complete round—for example, the second round of the 2026 competition in Kulm:

select 
	j.jumper_name as jumper,
	r.distance,
	rr.total_points,
	rr.pos
from round_results rr
join results r on 
	rr.race_id = r.race_id
	and rr.round_no = r.round_no
	and rr.jumper_id = r.jumper_id
join competitions c on rr.race_id = c.race_id
join jumpers j on rr.jumper_id = j.jumper_id
where 
	rr.race_id = find_race_id(2026, 'Kulm', 1)
	and rr.round_no = 2;

Results

Second round of the 2026 competition in Kulm:30 rows
jumperdistancetotal_pointspos
PREVC Domen228.5221.901
EMBACHER Stephan219.5214.302
TSCHOFENIG Daniel212.5204.603
KYTOSAHO Niko211197.304
ORTNER Maximilian205.5196.405
RAIMUND Philipp209.5195.806
SCHUSTER Jonas204.5193.407
AALTO Antti201191.608
BRESADOLA Giovanni202190.909
BICKNER Kevin204.5189.5010
DESCHWANDEN Gregor202188.0011
KOBAYASHI Ryoyu202186.1012
WELLINGER Andreas197186.0013
SUNDAL Kristoffer Eriksen200185.0014
FORFANG Johann Andre197183.7015
STOCH Kamil197183.6016
GEIGER Karl200.5183.3017
AIGRO Artti197.5181.9018
FOUBERT Valentin194181.3019
ZAJC Timi197181.0020
NAKAMURA Naoki196179.8021
LINDVIK Marius199179.1022
HAUSWIRTH Sandro197179.1023
KRAFT Stefan193176.3024
AMMANN Simon193168.1025
MIZERNYKH Ilya183167.4026
ZOGRAFSKI Vladimir185164.5027
SATO Yukiya182.5163.4028
MOGEL Zak179.5162.2029
INSAM Alex185161.8030