Please start any new threads on our new
site at https://forums.sqlteam.com. We've got lots of great SQL Server
experts to answer whatever question you can come up with.
Author |
Topic |
jkurzner
Starting Member
8 Posts |
Posted - 2012-03-04 : 17:27:47
|
I have a need to calculate soccer tables ("standings") for teams participating in a soccer league. I have been able to create the tables using total points as my ranking of teams with ties being broken by a series of tie breakers including score differntial, etc. My problem is that I need a way to compare head to head results as a tie breaking parameter. Suggestions would be great. |
|
sunitabeck
Master Smack Fu Yak Hacker
5155 Posts |
Posted - 2012-03-04 : 19:58:09
|
Hard to write a query without knowing your table structure and some sample data. And, hard (at least for me) to visualize a solution without writing a query. So I made up one, only to show what I am thinking:CREATE TABLE #tmp (team1 INT, team2 INT, result INT);INSERT INTO #tmp VALUES (1,2,1),(2,1,0); In this table, each game would have two entries - the example data that I have shows that team 1 played team 2 and that team 1 won. Then, to find the ranking, a query like this (which parses and runs - but don't know if the head-to-head resolution logic is correct):;WITH wins AS( SELECT team1, SUM(CASE WHEN result = 1 THEN 1 ELSE 0 END) AS wins FROM #tmp GROUP BY team1),InitialRankings AS( SELECT *, RANK() OVER (ORDER BY wins DESC) AS Standing FROM wins),HeadToHeadRankings AS( SELECT i.*, ISNULL(h.head_to_head_wins,0) AS head_to_head_wins FROM InitialRankings i OUTER APPLY ( SELECT SUM(CASE WHEN result = 1 THEN 1 ELSE 0 END) AS head_to_head_wins FROM #tmp a INNER JOIN InitialRankings s ON a.team2 = s.team1 -- all the opposing teams AND s.standing = i.standing -- that have the same ranking AND a.team1 = i.team1 -- as this team ) h)SELECT *,RANK() OVER(ORDER BY Standing,head_to_head_wins DESC) AS FinalRankFROM HeadToHeadRankings; |
 |
|
jkurzner
Starting Member
8 Posts |
Posted - 2012-03-05 : 10:06:10
|
I figured it might be difficult without some data or table structure.Here's a simplified structure and the current query that ranks the divisionsCREATE TABLE [dbo].[SCHEDULE]( [GNUM] [float] NULL, [DIVISION] [nvarchar](255) NULL, [FCODE] [nvarchar](255) NULL, [GDATE] [datetime] NULL, [HTEAM] [nvarchar](255) NULL, [VTEAM] [nvarchar](255) NULL, [PLAYED] [bit] NULL, [HRUNS] [float] NULL, [VRUNS] [float] NULL, [HFORFEIT] [bit] NULL, [VFORFEIT] [bit] NULL, [HCLUB] [nvarchar](255) NULL, [VCLUB] [nvarchar](255) NULL,) ON [PRIMARY]GO Presently there are two views that calculate the points because we allow for a maximum goal differential per game and also assign points to wins losses and ties. It's points that is the first ranking, then head-to-head, then maximum goal differential, etc. SELECT *, CASE WHEN GD > 3 THEN 3 WHEN GD < - 3 THEN - 3 ELSE GD END AS [MaxGD], CASE WHEN W = 1 THEN 3 WHEN D = 1 THEN 1 ELSE 0 END AS [Points]FROM (SELECT SCHEDULE.GNUM, SCHEDULE.DIVISION, SCHEDULE.HTEAM AS TeamID, CASE WHEN (Schedule.HRuns > Schedule.VRuns) OR (Schedule.VForfeit = 1) THEN 1 ELSE 0 END AS [W], CASE WHEN (Schedule.HRuns < Schedule.VRuns) AND (Schedule.VForfeit = 0) THEN 1 ELSE 0 END AS [L], CASE WHEN Schedule.HRuns = Schedule.VRuns THEN 1 ELSE 0 END AS [D], CASE WHEN Schedule.Hforfeit = 1 THEN 1 ELSE 0 END AS [Forfeit], Schedule.HRuns AS [GF], Schedule.VRuns AS [GA], Schedule.HRuns - Schedule.VRuns AS [GD], Teams.TCode, Teams.Team FROM SCHEDULE INNER JOIN TEAMS ON SCHEDULE.HTEAM = TEAMS.TCODE WHERE (SCHEDULE.PLAYED <> 0) AND (SCHEDULE.HFORFEIT = 0)) AS TUNIONSELECT *, CASE WHEN GD > 3 THEN 3 WHEN GD < - 3 THEN - 3 ELSE GD END AS [MaxGD], CASE WHEN W = 1 THEN 3 WHEN D = 1 THEN 1 ELSE 0 END AS [Points]FROM (SELECT SCHEDULE.GNUM, SCHEDULE.DIVISION, SCHEDULE.VTEAM AS TeamID, CASE WHEN (Schedule.HRuns < Schedule.VRuns) OR (Schedule.HForfeit = 1) THEN 1 ELSE 0 END AS [W], CASE WHEN (Schedule.HRuns > Schedule.VRuns) AND (Schedule.HForfeit = 0) THEN 1 ELSE 0 END AS [L], CASE WHEN Schedule.HRuns = Schedule.VRuns THEN 1 ELSE 0 END AS [D], CASE WHEN Schedule.Hforfeit = 1 THEN 1 ELSE 0 END AS [Forfeit], Schedule.VRuns AS [GF], Schedule.HRuns AS [GA], Schedule.VRuns - Schedule.HRuns AS [GD], Teams.TCode, Teams.Team FROM SCHEDULE, teams WHERE (Schedule.VTeam = Teams.TCode) AND (SCHEDULE.PLAYED <> 0) AND (SCHEDULE.VFORFEIT = 0)) AS TUNIONSELECT *, CASE WHEN GD > 3 THEN 3 WHEN GD < - 3 THEN - 3 ELSE GD END AS [MaxGD], 0 AS [Points]FROM (SELECT Schedule.gnum, Schedule.Division, Schedule.HTeam AS TeamID, 0 AS [W], 1 AS [L], 0 AS [D], 1 AS [Forfeit], 0 AS [GF], 3 AS [GA], - 3 AS [GD], Teams.TCode, Teams.Team FROM Schedule, Teams WHERE Schedule.HTeam = Teams.TCode AND Schedule.Played <> 0 AND HForfeit = 1) AS TUNIONSELECT *, CASE WHEN GD > 3 THEN 3 WHEN GD < - 3 THEN - 3 ELSE GD END AS [MaxGD], 0 AS [Points]FROM (SELECT Schedule.gnum, Schedule.Division, Schedule.VTeam AS TeamID, 0 AS [W], 1 AS [L], 0 AS [D], 1 AS [Forfeit], 0 AS [GF], 3 AS [GA], - 3 AS [GD], Teams.TCode, Teams.Team FROM Schedule, Teams WHERE Schedule.VTeam = Teams.TCode AND Schedule.Played <> 0 AND VForfeit = 1) AS T And here is the standings query which runs agains tht points query for creating the table viewsSELECT TOP (100) PERCENT DIVISION, TCode, Team, Wins, Losses, Draws, Forfeits, Points, GF, GA, MaxGD, Wins + Losses + Draws AS TotGms, CAST(Points / (Wins + Losses + Draws) AS decimal(6, 4)) AS AvgPts, CAST(MaxGD / (Wins + Losses + Draws) AS decimal(6, 4)) AS AvgGDFROM (SELECT DISTINCT TOP (100) PERCENT DIVISION, TCode, Team, SUM(W) AS Wins, SUM(L) AS Losses, SUM(D) AS Draws, SUM(Forfeit) AS Forfeits, CAST(SUM(Points) AS decimal(5, 0)) AS Points, SUM(GF) AS GF, SUM(GA) AS GA, CAST(SUM(MaxGD) AS decimal(5, 0)) AS MaxGD FROM dbo.PointsQuery GROUP BY DIVISION, TCode, Team ORDER BY DIVISION, Points DESC, MaxGD DESC) AS xORDER BY DIVISION, AvgPts DESC, AvgGD DESC Thanks for looking!! |
 |
|
theboyholty
Posting Yak Master
226 Posts |
Posted - 2012-03-05 : 12:28:57
|
jkurzner, have you found a solution to this yet?I've done a lot of work on league tables but for proper football in the UK and Europe, where usually league placings aren't worked out based on a head to head system, but I've got some ideas of how you might proceed.If you're sorted though, I'll leave it.---------------------------------------------------------------------------------http://www.mannyroadend.co.uk A Bury FC supporters website and forum |
 |
|
jkurzner
Starting Member
8 Posts |
Posted - 2012-03-05 : 12:37:40
|
Nope, I could still use help with this. |
 |
|
theboyholty
Posting Yak Master
226 Posts |
Posted - 2012-03-06 : 04:17:12
|
What are your table / field names? and how many teams are you likely to have in each league?Are you using a standard placings system i.e. Points (3 for a win, 1 for a draw) then Goal Difference then Goals Scored before relying on a head to head?---------------------------------------------------------------------------------http://www.mannyroadend.co.uk A Bury FC supporters website and forum |
 |
|
jkurzner
Starting Member
8 Posts |
Posted - 2012-03-06 : 08:53:58
|
Tables names are schedule, teams, clubs, division. Clubs have multiple teams and team participate in divisions. Divisions basically hold the name of the division and the points for wins, ties, etc. Therefore it is possible for each division to have different rules for ranking the standings. Right now the rules are all the same for the competitive divisions with 3 points for a win, 1 for a draw and 0 for a loss. In addition there is also the ability to record a forfeit to both teams (i.e. bad behavior) where both teams lose. There is a maximum goal differential of + or - 3 for each game. Therefore, if a team wins 8-0 they only get a +3 goal differential for that game and the loser only gets a -3. The sum of the goal differentials is a tie-breaking component. As far as head-to-head; that tie-breaker comes immediately after the average points ranking. We use average points to rank the teams because sometimes they don't all play the same number of games.Tie-breakers in order is:Average PointsHead-to-HeadAvg Goal DifferentialMost winsFewest lossesFewest goals allowed |
 |
|
theboyholty
Posting Yak Master
226 Posts |
Posted - 2012-03-06 : 09:46:48
|
That sounds pretty mental. My system is based on English and European leagues and we assume that all teams play all other teams exactly twice, once at home and once away. Teams are ranked by points, goal difference, goals scored and finally team name in alphabetical order (unless a major decision is based on it in which case a play off is occasionally used).Well as you said you'd done the rest of the code for ranking the teams so I'll just let you use this bit of code which I have and I'm sure you'll find a way of making it work for your own needs although it'll require a bit of minor tweaking.It builds a grid based on all the teams results against all the other teams. You can then JOIN this onto your existing table code and you should be able to tweak it to rank the teams where all other stats are the same.I'll run you through my table structure so you know what field and table names to change.All my data is in a single table called Prem.The fieldnames are:MatchID INT IDENTITY(1,1),MatchDate DATETIME,Home VARCHAR(30)--example 'Liverpool'Result VARCHAR(3)--example 3-1 (assume no-one ever scores more than 9 in a gameAway VARCHAR(30)--example 'Chelsea'I know its not the cleverest table structure and I should probably have used IDs relating to a Teams table, but I did what worked at the time.Anyway here's the SQL. I've coded it as a stored proc in order to keep the code tidy. As I'm sure you're aware, things can get pretty messy pretty quickly.CREATE PROCEDURE [dbo].[usp_Build_Comparison_Table]AS--get a distinct list of all teams in the league.IF (OBJECT_ID('tempdb..#Teams') IS NULL) BEGIN SELECT DISTINCT Home as TeamName INTO #Teams FROM Prem ORDER BY TeamName END--rebuild your results table (mine is called 'Prem'). The reason for this is to create a new field 'HAD' (to determine if it is a home win, away win or a draw). This--is neccessary because for a league containing 20 teams like the English Premier League, the dynamic SQL string was too long (over 8000 characters) when I did the--calculation in the query string. Its a bit of an ugly workaround.IF (OBJECT_ID('tempdb..#Prem') IS NULL) BEGIN --DROP TABLE #Prem SELECT * INTO #Prem FROM Prem ALTER TABLE #Prem ADD HAD CHAR(1) UPDATE #Prem SET Home = REPLACE(Home,' ','') UPDATE #Prem SET Away = REPLACE(Away,' ','') UPDATE #Prem SET HAD = CASE WHEN LEFT(Result,1) > RIGHT(Result,1) THEN 'H' WHEN LEFT(Result,1) = RIGHT(Result,1) THEN 'D' ELSE 'A' END END --@BuildComparisonTable is where we will build our dynamic sql string.DECLARE @BuildComparisonTable VARCHAR(8000)SET @BuildComparisonTable = 'SELECT TeamName 'SELECT @BuildComparisonTable = @BuildComparisonTable + ',SUM(' + REPLACE(TeamName,' ','') + ') as ' + REPLACE(TeamName,' ','') FROM #Teams SET @BuildComparisonTable = @BuildComparisonTable + ' FROM ( SELECT Home as TeamName'SELECT @BuildComparisonTable = @BuildComparisonTable + ',CASE WHEN Away = ''' + REPLACE(TeamName,' ','') + ''' THEN CASE WHEN HAD = ''H'' THEN 3 WHEN HAD = ''D'' THEN 1 ELSE 0 END + LEFT(Result,1) - RIGHT(Result,1) END AS ' + REPLACE(TeamName,' ','') FROM #Teams SET @BuildComparisonTable = @BuildComparisonTable + ' FROM #Prem UNION ALL SELECT Away as TeamName'SELECT @BuildComparisonTable = @BuildComparisonTable + ',CASE WHEN Home = ''' + REPLACE(TeamName,' ','') + ''' THEN CASE WHEN HAD = ''A'' THEN 3 WHEN HAD = ''D'' THEN 1 ELSE 0 END + RIGHT(Result,1) - LEFT(Result,1) END AS ' + REPLACE(TeamName,' ','') FROM #Teams SET @BuildComparisonTable = @BuildComparisonTable + ' FROM #Prem ) x GROUP BY TeamName'--if Comparison table exists already, drop itIF OBJECT_ID('ComparisonTable') IS NOT NULL BEGIN DROP TABLE ComparisonTable END--build a table from your list of teamsDECLARE @BuildTableSQL VARCHAR(1000)SET @BuildTableSQL = 'CREATE TABLE ComparisonTable (TeamName VARCHAR(20), 'SELECT @BuildTableSQL = @BuildTableSQL + '[' + TeamName + '] INT, ' FROM #teamsSET @BuildTableSQL = LEFT(@BuildTableSQL,LEN(@BuildTableSQL)-1)SET @BuildTableSQL = @BuildTableSQL + ')'exec(@BuildTableSQL)--execute the @BuildComparisonTable sql string to populate the table with a grid of results between the teams.--Here's how the scoring system works for this comparison table:--Its all done based on the home team. If a team has won they get three points plus a point for each unit of goal difference.--e.g. A team wins 3-1, they get 3 for the win, plus 2 for the goal difference.--a team gets one point for a draw--Teams get zero for a defeat MINUS the goal difference. e.g. a 1-0 defeat will result in a score of -1.--You could use any points system, it doesn't affect the main league table, rather it enables a comparison to be drawn between any two teams based on their results during the season.INSERT INTO ComparisonTableEXEC(@BuildComparisonTable) Good luck. I hope it helps.---------------------------------------------------------------------------------http://www.mannyroadend.co.uk A Bury FC supporters website and forum |
 |
|
jkurzner
Starting Member
8 Posts |
Posted - 2012-04-30 : 00:10:53
|
Sorry it took me so long to get back to this, but I've been focused on another project. This seems way above my capability at this point. I really didn't follow the example too well. I basically have the code to sort the teams and calculate the proper results with the exception of the comparison to determine how to order teams with a tie in the number of points. When that occurs and the teams dont play home and away or there are multiple teams tied, I am at a loss. Any chance you have another thought? |
 |
|
|
|
|
|
|