Ranking in Symfony/Doctrine - php

I have 3 entities in Symfony. Candidate, School, Game a candidate is assigned to one school and can start games. The games entity contains the score (of that) game. So far so good, here comes the tricky part:
I have to display to the candidates, what rank they have currently, as well as the top 10 players.
I know, I could use a native query for mysql and do something like proposed here: https://stackoverflow.com/a/3333697/2989952
But then, to get the rank of the candidate I'd have to load all entries from the db and loop throu them in PHP (or with Doctrines Criteria). Is there a better way to get the rank? I could also add a column point's or something in the candidate entity and update that after every game and then just sort by that column, still I'd have to loop throu all entries again in PHP.
Thanks for your help.

For the second part 'How to rank top 10 players' i would use index from loop with this query.
$gameRepository->findBy([],['score'=>'desc'],10);
For the first part.This looks promising and can be transfered into DQL easily.

Related

nested mysql queries with huge tables

I'm working on a management system for a small library. I proposed them to replace the Excel spreadsheet they are using now with something more robust and professional like PhpMyBibli - https://en.wikipedia.org/wiki/PhpMyBibli - but they are scared by the amount of fields to fill, and also the interfaces are not fully translated in Italian.
So I made a very trivial DB, with basically a table for the authors and a table for the books. The authors table is because I'm tired to have to explain that "Gabriele D'Annunzio" != "Gabriele d'Annunzio" != "Dannunzio G." and so on.
My test tables are now populated with ~ 100k books and ~ 3k authors, both with plausible random text, to check the scripts under pressure.
For the public consultation I want to make an interface like that of Gallica, the website of the Bibliothèque nationale de France, which I find pretty useful. A sample can be seen here: http://gallica.bnf.fr/Search?ArianeWireIndex=index&p=1&lang=EN&f_typedoc=livre&q=Computer&x=0&y=0
The concept is pretty easy: for each menu, e.g. the author one, I generate a fancy <select> field with all the names retrieved from the DB, and this works smoothly.
The issue arises when I try to add beside every author name the number of books, as made by Gallica, in this way (warning - conceptual code, not actual PHP):
SELECT id, surname, name FROM authors
foreach row {
SELECT COUNT(*) as num FROM BOOKS WHERE id_auth=id
echo "<option>$surname, $name ($num)</option>";
}
With the code above a core of the CPU jumps at 100%, and no results are shown in the browser. Not surprising, since they are 3k queries on a 100k table in a very short time.
Just to try, I added a LIMIT 100 to the first query (on the authors table). The page then required 3 seconds to be generated, and 15 seconds when I raised the LIMIT to 500 (seems a linear increment). But of course I can't show to library users a reduced list of authors.
I don't know which hardware/software is used by Gallica to achieve their results, but I bet their budget is far above that of a small village library using 2nd hand computers.
Do you think that to add a "number_of_books" field in the authors table, which will be updated every time a new book is inserted, could be a practical solution, rather than to browse the whole list at every request?
BTW, a similar procedure must be done for the publication date, the language, the theme, and some other fields, so the query time will be hit again, even if the other tables are a lot smaller than the authors one.
Your query style is very inefficient - try using a join and group structure:
SELECT
authors.id,
authors.surname,
authors.name,
COUNT(books.id) AS numbooks
FROM authors
INNER JOIN books ON books.id_auth=authors.id
GROUP BY authors.id
ORDER BY numbooks DESC
;
EDIT
Just to clear up some issues I not explicitely said:
Ofcourse you don't need a query in the PHP loop any longer, just the displaying portion
Indices on books.id_auth and authors.id (the latter primary or unique) are assumed
EDIT 2
As #GordonLinoff pointed out, the IFNULL() is redundant in an inner join, so I removed it.
To get all themes, even if there aren't any books in them, just use a left join (this time including the IFNULL(), if your provider's MySQL may be old):
SELECT
theme.id,
theme.main,
theme.sub,
IFNULL(COUNT(books.theme),0) AS num
FROM themes
LEFT JOIN books ON books.theme=theme.id
GROUP BY themes.id
;
EDIT 3
Ofcourse a stored value will give you the best performance - but this denormalization comes at a cost: Your Database now has the potential to become inconsistent in a user-visible way.
If you do go with this method. I strongly recommend you use triggers to auto-fill this field (and ofcourse those triggers must sit on the books table).
Be prepared to see slowed down inserts - this might ofcourse be okay, as I guess you will see a much higher rate of SELECTS than INSERTS
After reading a lot about how the JOIN statement works, with the help of
useful answer 1 and useful answer 2, I discovered I used it some 15 or 20 years ago, then I forgot about this since I never needed it again.
I made a test using the options I had:
reply with the JOIN query with IFNULL(): 0,5 seconds
reply with the JOIN query without IFNULL(): 0,5 seconds
reply using a stored value: 0,4 seconds
That DB will run on some single core old iron, so I think a 20% difference could be significant, and I decide to use stored values, updating the count every time a new book is inserted (i.e. not often).
Anyway thanks a lot for having refreshed my memory: JOIN queries will be useful somewhere else in my DB.
update
I used the JOIN method above to query the book themes, which are stored into a far smaller table, in this way:
SELECT theme.id, theme.main, theme.sub, COUNT(books.theme) as num FROMthemesJOIN books ON books.theme = theme.id GROUP BY themes.id ORDER by themes.main ASC, themes.sub ASC
It works fine, but for themes which are not in the books table I obviously don't get a 0 response, so I don't have lines like Contemporary Poetry - Etruscan (0) to show as disabled options for the sake of list completeness.
Is there a way to have back my theme.main and theme.sub?

Retrieve the mean of results from imputed data PHP/MySQL

First of, I'm pretty new to this site and coding in general so please explain in simple terms as I'm still learning! Thanks
Ok, so I've got a database of results. These are 1-6 ratings. I've already created the ability to retrieve certain results (user, group, all).
But now I'm wanting to alongside retrieving the group and all results to display at the top of the results a mean for each question.
So to start I'm wanting something like this I believe.
SELECT sum(r1), sum(r2), sum(r3) so on,
FROM table
This is where I get confused.
I think I'd need a variable to contain these and then another that counts the amount of entries to divide the total of r1 hence the mean.
Any ideas?..
To calculate a mean, use the AVG function, e.g.
SELECT AVG(r1), AVG(r2)
FROM table
See the MySQL docs.

PHP/MYSQL Database Logic Query

Trying not to reinvent the wheel here so thought 'i'd ask you guys;
There is an existing database for a computer game that records - map name, time it took to finish the map, the difficulty level, id of person. This database is used to record best finish times for each player. So the player can type a certain command and it shows the best finish times for a particular map.
Now i would like to create a ranking system that rewards the player points for finishing the maps based on the difficulty level, e.g completing it on easy rewards the player 1 point, 2 points for medium ,etc. This ranking system will show the top players with most points.
My question is, would it be better to use the current database and use PHP to accomplish the new ranking system
or
create a new database to accomplish it?
In either case, a simple logic example would be appreciated.
I think it is best to just use your already existing database. What do you mean logic example?
Could you try this:
SELECT *, (count(*) * difficulty) AS total
FROM `map`
GROUP BY user_id
ORDER BY total DESC
LIMIT 10
difficulty is a table field, which is 1 for easy, 2 for medium, etc.
As Kyle said use your current database/table and let php/ SQL do the work. I would do something
select player,
Map,
Count(*)
From mytable
Group by player, map
That should give you a count of player completes by map. Test this though. After you get the count you can loop through your counts and based on map multiply by the points awarded.

Row Number in Symfony Doctrine Query

I am storing data for lap times in a database, the data consists of a distance, time, average speed and max speed.
I am trying to display a leaderboard which shows the top ten people in whatever query you set (who has gone thr furthest in total, best average time etc). However, below the top ten I want to show the user who is logged ins position in the leaderboard. To do this I am trying to run the same query ordering my results and adding a ROW NUMBER to get the position.
I am using symfony 1.4 with the Doctrine ORM and I can't for the life of me figure out how to get row numbers in a query. I know you can do it in SQL like so:
SELECT full_name, ROW_NUMBER() OVER(ORDER BY distance) AS row_number
Yet I can't get it working in Doctrine Symfony.
Does anyone have any ideas on a way I can do this? (or even another way of going about it)
Thanks in advance.
Ok heres my solution:
I kind of found an answer to this after some more experimenting and collaborating.
What I did was get rid of the select and just return the ordered list of results;
$results = self::getInstance()->createQuery('r')
->orderBy('r.distance')
->execute();
Then I hydrated this result into an array and used array_search() to find the key of the result in the array (luckily I am returning user data here, and I know the user I am looking for in the array)
$index = array_search($user->toArray(), $results->toArray());
I can then return the $index + 1 to give me the users position in the leaderboard.
There is probably a better way to do this database side, but I couldn't for the life of me find out how.
If anyone has a better solution then please share.

SQL query to collect entries from different tables - need an alternate to UNION

I'm running a sql query to get basic details from a number of tables. Sorted by the last update date field. Its terribly tricky and I'm thinking if there is an alternate to using the UNION clause instead...I'm working in PHP MYSQL.
Actually I have a few tables containing news, articles, photos, events etc and need to collect all of them in one query to show a simple - whats newly added on the website kind of thing.
Maybe do it in PHP rather than MySQL - if you want the latest n items, then fetch the latest n of each of your news items, articles, photos and events, and sort in PHP (you'll need the last n of each obviously, and you'll then trim the dataset in PHP). This is probably easier than combining those with UNION given they're likely to have lots of data items which are different.
I'm not aware of an alternative to UNION that does what you want, and hopefully those fetches won't be too expensive. It would definitely be wise to profile this though.
If you use Join in your query you can select datas from differents tables who are related with foreign keys.
You can look of this from another angle: do you need absolutely updated information? (the moment someone enters new information it should appear)
If not, you can have a table holding the results of the query in the format you need (serving as cache), and update this table every 5 minutes or so. Then your query problem becomes trivial, as you can have the updates run as several updates in the background.

Categories