Calculate "most popular" ids in multiple database columns - php

On my site (aimed at PS3 gamers), users can select the 3 games they're currently playing. I will then work out which games are the most popular, and list the top 5 based on active users.
I have 3 columns in my users table called game1_id, game2_id and game3_id. These values are relational to the id column in another table called game, which contains all of the information on the games such as it's title.
How would I go about tallying up these totals and returning the top 5 most active games?
Here is my model function thus far:
function get_5_popular_games()
{
$this->db->select('user.game1_id, user.game2_id, user.game3_id');
$this->db->from('user');
$query = $this->db->get();
if ($query->num_rows() > 0)
{
return $query->result();
}
}
I assume I need to somehow tally up how many times a games id is found in any of the three columns (game1_id, game2_id, game3_id), but I'm not entirely sure how to go about it?
[EDIT] AND HERE IS THE FINISHED FUNCTION.
function get_popular_games($limit)
{
$this->db->select('count(*) as popularity, game.title');
$this->db->from('user_game');
$this->db->join('game', 'game.id = user_game.game_id');
$this->db->group_by('game_id');
$this->db->order_by('popularity', 'desc');
$this->db->limit($limit);
$query = $this->db->get();
if ($query->num_rows() > 0)
{
return $query->result();
}
}

Instead of having 3 columns, use a separate table to store the favorite games for each user. Then you could use a simple query like the following to get the top 5 games:
SELECT game_id, count( * ) as popularity
FROM users_favorite_games
GROUP BY game_id
ORDER BY popularity DESC
LIMIT 5;
Edit
Somebody didn't like this answer, so a little more context may be in order...
You could do what you want without changing your schema. Your query would look similar to the following:
SELECT game_id, count( game_id ) AS Popularity
FROM (
SELECT id, game1_id AS game_id
FROM user
UNION SELECT id, game2_id
FROM user
UNION SELECT id, game3_id
FROM user
) AS user_games
GROUP BY game_id
ORDER BY Popularity DESC;
However, the example in the question shows that you are using CodeIgniter's ActiveRecord, which does not support UNION. So you would end up with a more complicated query and some extra hacking around in your method. Not exactly desirable.

I think a better design would be to have a many-to-many table relating the user_id with the game_id, resulting in each user having three rows for their favorite games.

Related

Best average rating

I have created a simple rating system for news articles. The news articles are stored in the database table called 'articles'. Each article has a unique id, starting from 1.
So I have 2 articles, ID 1 and ID 2.
I also have a table called 'ratings' that takes the users unique ID, the article ID and the rating that the user gave.
If I give an article with ID 2 a 5/5 star rating, it goes into the 'ratings' table, with article ID 2, my user ID and the rating of 5.
I have figured out how to display the average rating of each article, but I would like to find out how to show the BEST average rating of articles in descending order. Is that at all possible? How could this be done?
Here is how I find the average:
<?
$votesForThis = 0;
$sql = "SELECT * FROM ratings WHERE articleID = ".$articleID." ORDER BY id ASC";
// Check if there are results
if ($result = mysqli_query($con, $sql)) {
// Loop through each row in the result set
while($row = mysqli_fetch_assoc($result)) {
$votesForThis++;
}
}
$result = mysqli_query($con, 'SELECT SUM(vote) AS vote_sum FROM ratings WHERE articleID=' . $articleID);
$row = mysqli_fetch_assoc($result);
$voteSum = $row['vote_sum'];
$averageVotes = $voteSum / $votesForThis;
?>
MySQL has an avg function you can use instead of implementing this logic yourself. From there, it's just a matter of grouping by the article ID and ordering by the average:
SELECT articleID, AVG(vote)
FROM ratings
GROUP BY articleID
ORDER BY 2 DESC
The best practice for doing this is to add a new column to your article table called average_rating and update it with a cron job or after every voting.
Keep in mind that after a while your rating table will become giant and calculating average rating on every page refresh will put huge load on your server.
I would use de-normalization in this one.
I would use triggers to update a previously created column on table articles which would store it's average rating.
I would have posted an example of trigger but you haven't posted which database are you using.
Mysql: https://dev.mysql.com/doc/refman/8.0/en/trigger-syntax.html
Postgresql: https://www.postgresql.org/docs/9.1/sql-createtrigger.html
Each time a rating it's done, updated or deleted a trigger would update this column with it's current average using the built-in avg function.
At the end you'll only have to create a select on the articles table ordered by this rating column desc.
And create an index on this average rating column to have even faster results.
SELECT articleID, AVG(vote)
FROM ratings
GROUP BY articleID,vote
ORDER BY DESC
used this Query

codeigniter poll module using hmvc

I'm trying create a poll module within codeigniter using hmvc
I'm trying to return the count as percentages against the amount of user_id's that have voted into the candidate_id
I'm using a so called perfectcontroller and perfectmodel
where I can return the count on the number of rows each candidate has a user_id
but I have seven candidate_id's so getting the percentages is a little tricky
but its also tricky trying to get the count to match up against the id its counting
I do not know if this is the right way to go about it or if I should be doing it another way any suggestions would help
thanks
<?php
function get_result(){
$query = $this->get_where_custom('candidate_id',$candidate_id);
$num_rows = $query->num_rows();
//returns the amount of user votes
foreach($query->result() as $row){
$candidate_id = $row->candidate_id;
}
}
?>
No matter if you have 7 or 77 candidates. Votes are counting. So if you have only one vote in DB, it means 100% for one candidate and 0% for rest of them. I don't know what do you consider by perfectcontroller and perfectmodel. But your table could be like
election_id | voter_id | candidate_id
your query would be like
$this->db->query("SELECT `e`.`candidate_id`, COUNT(1) AS `total`, COUNT(1) / `t`.`cnt` * 100 AS `percentage` FROM `elections` `e` CROSS JOIN (SELECT COUNT(1) AS `cnt` FROM `elections`) `t` GROUP BY `e`.`candidate_id`;");

How to select all from MySQL table where column between dates and order by column

I am working on a project to build an assessment leader board for a learning centre. I've got a table that looks like this
I am trying to write a query which will select all from the table by CLASS, within DATE range, add score per individual STUDENT_ID and then order in descending order by the added score to create the leader board. I've read a little on sub queries but can't quite understand the examples or exactly how they work, I also think I would need a SELECT DISTINCT student_id in my query but my knowledge here is also limited as I have only used it once.
Anyway this is what I have so far.
$classcheck = mysql_query("SELECT *
FROM assessment
WHERE class = '$class_info'
order by score DESC")
or die(mysql_error());
if(mysql_num_rows($classcheck) > 0){
while($row = mysql_fetch_array($classcheck)){
if(strtotime($row["date"]) > strtotime($fromdate) && strtotime($row["date"]) < strtotime($todate)){
echo $row['score'].'<p>';
}
}
}
But I need it to add SCORE and order by the added SCORE in the query somewhere which I cannot achieve with what I have written.
I know should start using PDO rather than mysql_query, knowledge limited again and I am running out of time. All feedback would be greatly appreciated. OH, and the score is really a percentage.
You don't need a subquery, you just need SUM and GROUP BY to total the scores by student, and a WHERE clause to restrict the dates.
SELECT student_name, SUM(score) AS total_score
FROM assessment
WHERE date BETWEEN '$fromdate' AND '$todate'
AND class = '$class_info'
GROUP BY student_id
ORDER BY total_score DESC
I think a GROUP BY might do the trick since you are trying to add up all the scores of an individual STUDENT_ID.
Please feel free to correct me if I'm wrong but the following SQL should get you what you are looking for.
SELECT SUM(score) AS ttl_score, student_name
FROM assessment
WHERE class='$class_info'
AND date>='$start' AND date<='$end'
GROUP BY student_id ORDER BY ttl_score DESC;
$classcheck = mysql_query("Select Student_id, sum(Score) as SummedScore from assessment where class='$class_info' and
date between '$fromdate' and '$todate' Group by Student_ID Order By SummedScore"
or die(mysql_error());
if(mysql_num_rows($classcheck) > 0){
while($row = mysql_fetch_array($classcheck)){
echo $row['SummedScore'].'<p>';}
}

Active Record - CodeIgniter : multiple COUNT on 3 joined tables

Working on a project with CodeIgniter and Active Record.
I face an issue with a query :
I have 3 tables in my database which I would like not only to join but also to make countS on 2 of them. The tables are respectively linked with user_id, store_user_id, event_user_id fields
Table 1 : user
user_id
Table 2 : store
store_user_id
Table 3 : event
event_user_id
What I am trying to do is to :
1 - Get all the data from user
2 - Count the number of stores with store_user_id = user_id (it may be 0)
3 - Count the number of events with event_user_id = user_id (it may be 0)
I have done this in my function :
$this->db->select('*');
$this->db->select('COUNT(s.store_user_id) as total_store', FALSE);
$this->db->select('COUNT(e.event_user_id) as total_event', FALSE);
$this->db->from('user u');
$this->db->join('store s', 's.store_user_id = u.user_id', 'left'); // this joins the user table to store table
$this->db->join('event e', 'e.event_user_id = u.user_id', 'left'); // this joins the user table to event table
$this->db->group_by('u.user_id');
$q = $this->db->get();
if ($q->num_rows()>0){
foreach ($q->result() as $rows) {
$data[] = $rows;
}
return $data;
}
The trouble is that when I display total_store and total_event in my view, the results are the same and I think figures are multiplied between them..
For example :
For an user I have 3 events en 4 stores, the results displayed will be total_event = total_store = 12 ...
I don't understand why and it makes me crazy for hours!! Moreover, when I make only one count, the result is correct..
Any idea??
Many thanks in advance :)
Lastly I have implemented this basic SQL query :
$this->db->query('SELECT
u.*,
x.total_store,
y.total_event
FROM
user u
LEFT OUTER JOIN (SELECT s.store_user_id, COUNT(s.store_user_id) AS total_store
FROM store s
GROUP BY s.store_user_id) x ON x.store_user_id = u.user_id
LEFT OUTER JOIN (SELECT e.event_user_id, COUNT(e.event_user_id) AS total_event
FROM event e
GROUP BY e.event_user_id) y ON y.event_user_id = u.user_id
');
Hope it will helps others
When you count() you're counting the number of rows, not the number of distinct values in the result set. You're right that the number is being multiplied: there's a row in your resultset for each user-store-event combination.

select 3 rows from mysql table, then get each row's specific column

Select 3 rows from table1
Get a specific column data out of each row.
Then use that each column data obtained , to make a query again to get data from table2.
Store the data obtained in step 4 into a variable for each row.
Then put them in json array (table 1 , 3 rows + table 2's data(each of them).
I am building a rank table, it displays top 3 users with their rank name.
For example:
User1 has 2000 points , user 2 has 4000points , user 3 has 10k points , so the top 3 user is :
user 3 > user 2 > user 1
So , i want the php to go to 'users' table and get the top 3 members using this:
$query = mysql_query("SELECT * FROM users ORDER BY pts DESC LIMIT 3");
$rows = array();
while($r = mysql_fetch_assoc($query)) {
$rows[] = $r;
}
Table structure for 'user':
1.username(varchar)
2.pts(int)
After the rows are put into an array , how can i get 'points' for each of the row in that array.
Then go to 'rank' table to get their ranknames.
Table structure for 'rank':
1.rank(varchar)
2.pts(int)
Inside rank table there is 'pts' to let php choose compare which rank the user is at based on the points from each row of the array.
Normally i would use this if its only for 1 user , but for multiple users , im not sure:
$result = mysql_query("SELECT * FROM rank WHERE pts <= '$upts' ORDER BY pts DESC LIMIT 1")
or die(mysql_error());
Then after getting the rank for the top 3 users , php will now add the ranks to each of the user(row) in that array(of course , add it to the rank owner, not just simply place it in).
Then JSON encode it out.
How can i do this?
I am not sure if this is what you want. That is combine the two query into one query. Please take a look at http://sqlfiddle.com/#!2/ad419/8
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts group by user.id
UPDATED:
For extracting top 3, could do as below;
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts
GROUP BY user.id
ORDER BY pts DESC LIMIT 3
If i understand correctly, you need to get values from Rank and Users tables. In order to do that in just one query You need to add FK (Foreign Key) to the Rank table that points to a specific user in the Users table.
So you need to add userId to the Rank table and then you can run:
SELECT r.rank, u.points from users u,rank r where u.userId = r.userId
This is roughly what you need.
Not quite the answer to your exact question, but this might be of use to you: How to get rank using mysql query. And may even mean that you don't require a rank table. If this doesn't help, I'll check back later.
Use this query
$query = "SELECT
u.pts,
r.rank
FROM users as u
left join ranks as r
on r.pts = u .pts
ORDER BY pts DESC
LIMIT 3";
This will bring what you required without putting into an array
$rec = mysql_query($query);
$results = arrau();
while($row = mysql_fetch_row($rec)){
$results[] = $row;
}
echo json_encode($results);
It looks like what you're trying to do is retrieve the rank with the highest point requirement that the user actual meets, which isn't quite what everyone else is giving here. Fortunately it is easily possible to do this in a single query with a nice little trick:
SELECT
user.username,
SUBSTRING_INDEX(GROUP_CONCAT(rank.rank ORDER BY pts DESC),",",1) AS `rank`
FROM user
LEFT JOIN rank ON user.pts >= rank.pts
GROUP BY user.id
ORDER BY pts DESC
LIMIT 3
Basically what the second bit is doing is generating a list of all the ranks the user has achieved, ordering them by descending order of points and then selecting the first one.
If any of your rank names have commas in then there's another little tweak we need to add on, but I wouldn't have thought they would so I've left it out to keep things simple.

Categories