performing calculations on a database - php

I have a database setup with some sample data. I have a number of teams in one table and their fixtures in another in their fixtures I have their scores for the games they have played already. I am struggling with the logic of calculating their wins draws and losses. should I update this in the teams table when updating the results (in fixtures table) or calculate it from scores in the fixtures table. I was reluctant to do it the first way as it may be concidered duplicate data but can't figure out the logic of how to calculate it. as you can probably tell this is the first database I have worked on with relationships between tables.
I am trying to present data from the above tables in to a league table. in order to get points I need to calculate games won/d/lost and that is what I can't figure out how to do (count the number for times home team out socres away team etc)
I will remove most cols from teams if I can calculate it from fixtures table.

Datasets and calculations relating to various flavors of sportsball are surprisingly complex. I've written code to generate fixture schedules based on arena availability, and it's not fun.
Anyhow, in order to generate the report you're after without duplicating data all over the place something like the below should work, though I haven't been able to test it.
SELECT t.team_name,
hr.home_win, hr.home_loss, hr.home_draw,
ar.away_win, ar.away_loss, ar.away_draw
FROM teams t
-- home record
INNER JOIN (
SELECT home_team AS 'team_name',
SUM(IF(home_team_score>away_team_score,1,0)) AS 'home_win',
SUM(IF(home_team_score<away_team_score,1,0)) AS 'home_loss',
SUM(IF(home_team_score=away_team_score,1,0)) AS 'home_draw'
FROM fixtures
GROUP BY home_team
) hr ON t.team_name = hr.team_name
-- away record
INNER JOIN (
SELECT away_team AS 'team_name',
SUM(IF(home_team_score<away_team_score,1,0)) AS 'away_win',
SUM(IF(home_team_score>away_team_score,1,0)) AS 'away_loss',
SUM(IF(home_team_score=away_team_score,1,0)) AS 'away_draw'
FROM fixtures
GROUP BY away_team
) ar ON t.team_name = ar.team_name
Now, a normal RDBMS would just use COUNT(scoreA>scoreB), but since this is MySQL I had to fudge it with SUM(IF()). fiddle
Assuming that you're not going to have thousands of teams this should scale reasonably well.

Related

Breaking a complex query into temporary tables in MYSQL

I am developing a personal proyect for academic books. I have some tables with +30.000 rows each for works, editions, authors and so on. All the information of the books —genres, subjects, authors, publishers, etc— is spread over a lot of tables with different types of relations.
I have a query for the main page that works, but the site takes six seconds to load. A lot of time… I was wondering which would be the proper approach for obtaining all the data I need with temporary tables.
What I want to do now is to join the temporary table _work with the related data of another table, say «genre». But the relationship between «work» and «genre» is done with the temporary table «work_has_genre».I know how to do that with normal tables in a single query:
SELECT *
FROM work a
LEFT JOIN (
SELECT GROUP_CONCAT(f_a.id SEPARATOR '|') AS genre_id, GROUP_CONCAT(f_a.genre SEPARATOR '|') AS genre_name, f_b.work_id AS _work_id
FROM genre f_a
INNER JOIN (
SELECT *
FROM work_has_genre f_b_a
) f_b
ON f_a.id=f_b.genre_id
GROUP BY f_b.work_id
) f
ON a.id=f._work_id
WHERE a.id=13
I suppose the idea would be to break this actions in parts, but I don't know how. Could someone help me with a bit of pseudocode? Or maybe this is not the best approach. Any idea will be very welcomed!
A.
As I said in comments, I would first suggest reworking/flattening the subqueries as much as possible first, but once you get to semi-independent aggregations temp tables can be helpful.
Generally, the pattern is to put each such aggregation subquery's results into it's own temp table (with an index on the field the subquery was joined to the main query on) even if that means adding tables (and the main query's WHERE) to the original subquery, and then joining to the temp table in the main query.

Calculation of Points in a Database in 3rd NF

I have a database where the results from a shooter game are stored. I put them to 3NF to allow extensions of the system. So it looks like this:
Player
-------------------
GameId integer
PlayerId integer
TeamId integer
Hits
-------------------
GameId integer
FromId integer
ToId integer
Hits integer
So basically for every game there is a ID and every Player and Team has its ID (with their names stored in other databases)
Now I want to calculate points for each player. I need the points for each game but more importantly the total per player. The points are basically: 3 Points for each hit on opponent, -2 points for each hit of a team member and -2 points for each hit taken.
Alone the calculation of the number of team hits requires a JOIN with 3 tables and I fear for performance in production environment. (Each game has ~8 players-> PlayerDB-Size is 8n and HitsDB-Size is (8-1)^2*n)
And at the end: I need to calculate the points per player for each game and sum those up because the minimum points per game should be zero. And finally get a rank for each player (player x has the 2nd most total points etc)
I feel like I'm getting lost in overly complicated queries that will kill the database' performance at some point.
Could anyone judge the design and maybe give me some pointers where to start looking further? I though about storing the TeamHits and Points per Game in the players Database (Points for summing over them, teamHits for statistical purposes) but that would of course break normalization.
PS: I'm working with PHP 5 and MYSQL. I also thought about getting each game from the database, calculating the points in PHP (which I'm already doing when I show the game) and writing this back (optimally on putting in the game to the DB but also when the parameters for the points change)
Edit: Idea to avoid subselects would be:
SELECT p.*, SUM(h.Hits) AS TeamHits, SUM(h2.Hits) as Hits
FROM player p
LEFT JOIN
(hits h
INNER JOIN player p2
ON h.GameId=p2.GameId AND h.ToId=p2.PlayerId
)
ON p.GameId=p2.GameId AND h.FromId=p.PlayerId AND p.TeamId=p2.TeamId
GROUP BY p.PlayerId, p.GameId
LEFT JOIN hits h2
ON h2.GameId=p.GameId AND h2.FromId=p.PlayerId
But of course this does not work. Is it even possible to combine groupings with joins or will I have to use subqueries?
Best I have is:
SELECT p.PlayerId, SUM((-2-3)*IFNULL(th.TeamHits, 0) + (3)*IFNULL(h.Hits, 0) + (-2)*IFNULL(ht.HitsTaken, 0)) AS Points
FROM player p
LEFT JOIN
(SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS TeamHits
FROM player p
INNER JOIN hits h
ON h.GameId=p.GameId AND p.PlayerId=h.FromId
INNER JOIN player p2
ON p.GameId=p2.GameId AND p2.PlayerId=h.ToId AND p.TeamId=p2.TeamId
GROUP BY p.PlayerId, p.GameId) th
ON p.GameId=th.GameId AND p.PlayerId=th.PlayerId
LEFT JOIN
(SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS Hits
FROM player p
INNER JOIN hits h
ON h.GameId=p.GameId AND p.PlayerId=h.FromId
GROUP BY p.PlayerId, p.GameId) h
ON p.GameId=h.GameId AND p.PlayerId=h.PlayerId
LEFT JOIN
(SELECT p.GameId, p.PlayerId, SUM(h.Hits) AS HitsTaken
FROM player p
INNER JOIN hits h
ON h.GameId=p.GameId AND p.PlayerId=h.ToId
INNER JOIN player p2
ON p.GameId=p2.GameId AND p2.PlayerId=h.FromId AND p.TeamId!=p2.TeamId
GROUP BY p.PlayerId, p.GameId) ht
ON p.GameId=ht.GameId AND p.PlayerId=ht.PlayerId
GROUP BY p.PlayerId
Fiddle: http://sqlfiddle.com/#!9/dc0cb/4
Current problem: For a database with about 10,000 games calculating the points for all players takes about 18s. This is unusable, so I need to improve this...
Joins are not that expensive, subqueries are. as long as you can avoid subqueries you're not hitting too bad.
Remember, a database is built for this stuff these days.
Just make sure you have the proper indexes on the right fields so its optimised. Like teamID and GameID and playerID should be indexes.
Just run it in phpmyadmin and see how many milliseconds it takes to execute. if it takes more than 50 its a heavy query, but usually its pretty hard to hit this... I once managed to make a very heavy query that joined 100.000+ rows out of different tables and views and still did that in 5ms...
What numbers of requests a hour are we talking about? 200 players a day? 200.000 players a day? How often do the requests happen? 10 per second per player? once a minute? how loaded is your database?
I think that all these parameters are low, so you shouldnt worry about this optimisation yet.
Get your game up and running, clean up the php code where real gains can be had, and stay clear of complex subqueries or views.
As long as your table does joins and unions its pretty darn fast. and if you must do a subquery see if there is not an alternative way by using a linking table to link certain results to certain other tables so you can do a join instead of a subquery.

SQLite select multiple foreign keys to one row

I'm trying to set up a high scores board for a game I'm making. On the board, I'm using foreign keys to two other boards, players and weapons. Each score stores the four weapons the player used on that run. The tables are set up like this:
Scores
id|playerid|score|weapon0id|weapon1id|weapon2id|weapon3id
Players
id|name
Weapons
id|name
I want to select multiple rows from the scores table with ids replaced by the appropriate names. I'm able to get the correct player name and one weapon using this statement:
SELECT scoreID, Players.playerName, scoreVal,
Weapons.weaponLabel, scoreW1, scoreW2, scoreW3
FROM Scores, Players, Weapons
WHERE Players.playerID = scorePlayer AND Weapons.weaponID = scoreW0
Everywhere I've looked shows that to be the best way to get a value from a row referred to by a foreign key. It works fine for the player name, but there seems to be no way to expand this to fill in multiple weapon names at once. Using an OR with the remaining weapons or using weaponID IN (w0,w1,w2,w3) seems to get one row for each weapon, not one row with each weapon in the appropriate spot.
Is there any way to get the correct weapon names just using the select statement? Or will I need to have extra code loop through and replace each weapon id with the correct name?
This design is questionable: weapon0..n will likely lead to nothing but difficult queries like this. The queries will also have to be de-normalized - e.g. one join per weapon0..n.
Anyway, the query is wrong and will return many more rows than desired because it uses the form FROM a,b which implies a CROSS JOIN between a and b and there is not appropriate selectors in the WHERE to make it an equi-join. Try to use a normal (INNER) JOIN and ON to make each join more apparent:
SELECT s.scoreID, p.playerName, s.scoreVal,
w0.weaponLabel as w0Label,
w1.weaponLabel as w1Label
-- etc
FROM Scores s
JOIN Players p ON p.id = s.playerID
JOIN Weapons w0 ON w0.weaponID = s.scoreW0
JOIN Weapons w1 ON w1.weaponID = s.scoreW1
-- etc, ick!!!
By now it should become apparent why the de-normalized data is icky!
Each column must be joined with a different relation (w0, w1, etc).
I usually have to create a looping procedure to get all the denormalized columns in one row per unique set, in your case player, weaponlabel.

Get and order based on most recent entry while in multiple mysql joins

I have the following statement that finds all the players of a team in the current season. The players are ordered by their handicap. If their handicaps are the same they are ordered by the oldest added_date, meaning newer members are lower down the list.
SELECT players.playerid_p,
players.fname,
players.sname,
players.tel,
players.mob,
players.email,
season_players.captain
FROM season_players
LEFT JOIN players ON (season_players.playerid_f = players.playerid_p)
LEFT JOIN handicaps ON (handicaps.playerid_f = players.playerid_p)
WHERE season_players.seasonid_f = '$currentSeason'
AND season_players.teamid_f = '".$row["teamid_p"]."'
GROUP BY players.playerid_p
ORDER BY handicaps.handicap ASC, handicaps.added_date ASC
The handicaps table can have multiple entries per player for any reviews they have had.
I can't figure out how to make the latest handicap to be used for the ordering (something maybe to do with MAX on added_date?) yet at the same time if two or more handicaps are the same it order them by oldest registered first based on added_date.
In stead of trying to figure out how to satisfy your needs in current query, wouldn't it be an option to create a separate table, let's say "Handicaps_latest", which only stores player_id and required info of latest review. The reason for doing this is because you are only trying to get(columns in your select clause) the information about players and nothing really needed from handicaps table. In this case, a handicaps table with multiple entries per player might not be a good table to join. But considering that those data might be required in other logic, so leave them there and create a branch new table only storing latest review data could be an option for your case. But it requires some extra work apparently, that is, whenever you insert a new entry into your original handicpas table, a particular entry in handicaps_latest needs to be updated.
SELECT x.*
FROM my_table x
JOIN
( SELECT id,MAX(other_column) max_other_column FROM my_table GROUP BY id) y
ON y.id = x.id
AND y.max_other_column = x.other_column;

Creating Soccerleagueranking with Codeigniter & Mysql

I've searched the site for similar posts but i found just one where the developer tried to do his calculations (win-lose-draws) with an enormous SQL query. I would like to do the calculations in my controller but don't really know where to start.
I have 2 tables which look like this:
Teams
teamID teamName
Games
gameID matchday homeTeamID awayTeamID homeScore awayScore
Now i'm trying to produce a league ranking out of this match results, But i need some insights on how to look at this...
At the moment, I have a query which selects all the match results and assigns the correct teamID's to the home or away Team like this:
"SELECT g.gameID, g.matchday, g.homeTeamID, g.awayTeamID, g.homeScore, g.awayScore, th.teamName as homeTeam, ta.teamName as awayTeam,
FROM games AS g
INNER JOIN teams as th ON g.homeTeamID = th.teamID
INNER JOIN teams as ta ON g.awayTeamID = ta.teamID
JOIN submenu_teams AS s ON g.submenuID = s.submenuID"
Can anybody try to explain where to go from here to get a nice ranking of the teams according to how many points they won during the season?
Thnx!
I would suggest to keep track of the points in a table (season1) so that every time a page is requested, you don't have to compute the rankings again : you just fetch from the table.
Everytime a new match is played, run a script that adds X point to winner and substracts Y points from loser.
To display, fetch the results and order by score.
You're done !
(was it my post that you read on rankings and SQL ?)

Categories