I have a score table that has the following stucture:
CREATE TABLE IF NOT EXISTS `game_scores` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`game_id` int(11) NOT NULL,
`score` int(11) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=329 ;
I am trying to generate a list of top players ordered by the number of first place high scores they have.
Assumptions:
Higher scores are better. (Otherwise, use MIN instead of MAX)
The issue here is there are multiple games, denoted by game_id. Therefore each game_id should have its own first-place user(s)
You can get first-place scores by:
SELECT MAX(score) score, game_id
FROM game_scores
GROUP BY game_id
You can then get users with first-place scores by joining on this aggregate. This should also properly include ties:
SELECT s.user_id
FROM game_scores s
INNER JOIN (
SELECT MAX(score) score, game_id
FROM game_scores
GROUP BY game_id
) fp ON s.game_id = fp.game_id AND s.score = fp.score
You can then count these up for each user and order by the count:
SELECT s.user_id, COUNT(*) first_place_scores
FROM game_scores s
INNER JOIN (
SELECT MAX(score) score, game_id
FROM game_scores
GROUP BY game_id
) fp ON s.game_id = fp.game_id AND s.score = fp.score
GROUP BY s.user_id
ORDER BY COUNT(*) DESC
Assuming that a score of 1 means high score, the following should do what you want:
SELECT count(user_id), user_id FROM game_scores WHERE score="1" GROUP BY user_id ORDER BY count(user_id) DESC
Related
I have a query that returns the last 10 news and last 10 comments to each of them from the database.
SQL:
CREATE TABLE news (
news_id INT UNSIGNED AUTO_INCREMENT,
subject VARCHAR(128) NOT NULL,
article TEXT NOT NULL,
type_id TINYINT UNSIGNED NOT NULL DEFAULT 1,
news_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (news_id)
);
CREATE TABLE comments (
comment_id INT UNSIGNED AUTO_INCREMENT,
message TEXT NOT NULL,
news_id INT UNSIGNED NOT NULL,
msg_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (comment_id),
CONSTRAINT fk_comments_news_id FOREIGN KEY (news_id)
REFERENCES news (news_id)
ON DELETE CASCADE
);
SELECT news_id, comment_id, rnk
FROM (
SELECT
*,
#rnk:=IF(#prev_news_id=news_id, #rnk+1, 1) rnk,
#prev_news_id:=news_id
FROM (
SELECT
lastnews.*,
cmts.comment_id,
cmts.message,
cmts.msg_time
FROM (SELECT * FROM news WHERE type_id=5 ORDER BY news_id DESC LIMIT 10) lastnews
LEFT OUTER JOIN comments cmts ON cmts.news_id=lastnews.news_id
ORDER BY cmts.news_id DESC, cmts.comment_id DESC) t1) t2
WHERE rnk <= 10;
PHP:
$query = <<<_END
SELECT news_id, comment_id, rnk
FROM (
SELECT
*,
#rnk:=IF(#prev_news_id=news_id, #rnk+1, 1) rnk,
#prev_news_id:=news_id
FROM (
SELECT
lastnews.*,
cmts.comment_id,
cmts.message,
cmts.msg_time
FROM (SELECT * FROM news WHERE type_id=5 ORDER BY news_id DESC LIMIT 10) lastnews
LEFT OUTER JOIN comments cmts ON cmts.news_id=lastnews.news_id
ORDER BY cmts.news_id DESC, cmts.comment_id DESC) t1) t2
WHERE rnk <= 10;
_END;
$db = new PDO('mysql:='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS);
$dbh = $db->prepare($query);
$dbh->execute();
$data = $dbh->fetchAll(PDO::FETCH_NUM|PDO::FETCH_GROUP);
print_r($data);
The query works perfectly in MySQL cmd, but returns wrong data in PHP. The problem is in rnk row. It always equals 1. Why is that?
UPDATE: I just restarted MySQL Command Line and it broke even there. But then I turned on profiling with SET profiling=1 and suddenly it works again. What is more, turning off profiling doesn't negate the effect.
It turns out the problem was with user-defined variables initialization. We should do it explicitly to avoid undefined behavior.
In our case we add one more join:
FROM (SELECT * FROM news WHERE type_id=5 ORDER BY news_id DESC LIMIT 10) lastnews
LEFT OUTER JOIN comments cmts ON cmts.news_id=lastnews.news_id
INNER JOIN (SELECT #rnk:=0,#prev_news_id:=0) t3
I'm trying to create a ranking table based on how many likes/upvotes a user had on all his items in total. User in the upvotes table links to id of the user that made the like, but I think you don't need this.
Hopefully by giving these tables everything will get clear.
I think the trick here is to get all the upvotes by each item and merge them together towards a user this item was from to get a total likes for each user and then rank all the users based on this total. Of course doing this will probably be a slow query so I need a very performant way to handle this.
The hard thing is here mainly that the upvotes table doesn't include the user id.
3 tables:
CREATE TABLE `items` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`user_id` int(255) NOT NULL,
`img` varchar(500) NOT NULL,
`message` varchar(200) NOT NULL,
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`active` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=19 DEFAULT CHARSET=latin1;
CREATE TABLE `upvotes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(255) NOT NULL,
`item_id` int(255) NOT NULL,
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`password` binary(60) NOT NULL,
`first_name` varchar(255) NOT NULL,
`last_name` varchar(255) NOT NULL,
`active` int(1) NOT NULL DEFAULT '1',
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
I need a performant query giving me the ranking of each user ranked on how many likes they got on all their items?
I managed to write this:
SELECT #rank := #rank + 1 AS rank, m.*
FROM (SELECT
users.first_name as first_name,
users.last_name as last_name,
count(upvotes.item_id) as total
FROM upvotes
INNER JOIN users
ON users.id = (SELECT items.user_id FROM items WHERE items.id = upvotes.item_id LIMIT 1)
GROUP BY users.id
ORDER BY total DESC
) m, (SELECT #rank := 0) r
But I reckon this will be super slow when the database grows...
You can do a simple join query in order to get the total likes for each item of user and order your results with the resulting count in descending order
SELECT u.*,i.*,COUNT(DISTINCT up.user) `total_user_likes_item`
FROM users u
JOIN items i ON(i.user_id = u.id)
JOIN upvotes up ON(up.item_id = i.id)
GROUP BY u.id,i.id
ORDER BY u.id,i.id,total_user_likes_item DESC
Edit from comments For user total likes you remove i.id from group by as below query
SELECT u.*,COUNT(DISTINCT up.user) `total_user_likes_item`
FROM users u
JOIN items i ON(i.user_id = u.id)
JOIN upvotes up ON(up.item_id = i.id)
GROUP BY u.id
ORDER BY total_user_likes_item DESC
I'll try answer your question:
In table users you can add row sum_upvotes. Every time when someone get one like (vote) you will increment this column by:
UPDATE users
SET sum_upvotes = sum_upvotes + 1
;
Of course, you will insert a column in table upvotes.
Finally, you query to select users and order them by upvotes will look like this
SELECT first_name, last_name
FROM users
ORDER BY sum_upvotes
;
Hope this helps.
I'm working on a PHP project with MYSQL database. I have a table of groups of students. Each group has an examiner. What i want to do is that i want to set two examiners for each group randomly. How to do it?
MySQL Code:
create table groups (
groupID int(10) not null,
nbStudents int not null,
avgGPA DOUBLE NOT NULL,
projectName varchar(50) not null,
advisorID int,
examiner1ID int,
examiner2ID int,
adminID int not null,
primary key (groupID)
);
create table faculty (
name varchar(30) not null,
facultyID int(10) not null,
email varchar(30) not null,
mobile int(15) not null,
primary key (facultyID)
);
examiner1ID and examiner2ID are foreign keys from the table faculty.
Here is a very convoluted way to do it. It uses 2 subqueries to pick faculty members, and insert .. on duplicate key to update the examiners IDs.
insert into groups
(groupID, examiner1ID, examiner2ID)
select groupID,
#x:=(select facultyID from faculty order by rand() limit 1),
(select facultyID from faculty where facultyID <> #x order by rand() limit 1)
from groups
on duplicate key update examiner1ID=values(examiner1ID), examiner2ID=values(examiner2ID);
#x is a user-defined-variable. In this case, it is used to store the first random faculty member. <> #x makes sure we don't pick the same faculty member in both slots.
Since groupID is a unique key, when we try to insert a row with an existing unique key, it will update the existing row instead of inserting it. That's what on duplicate key update clause is used for.
set different examiners for each group:
insert into groups
(groupID, examier1ID, examier2ID)
select a.groupID, max(if(b.id%2, b.facultyID, 0)), max(if(b.id%2, 0, b.facultyID))
from (
select #row:=#row+1 id, groupID
from groups a
join (select #row:=0) b) a
join (
select #row:=#row+1 id, facultyID
from (
select facultyID
from faculty a
order by rand()) a
join (select #row:=0) b) b on a.id = ceil(b.id/2)
group by a.groupID
on duplicate key update examiner1ID=values(examiner1ID), examiner2ID=values(examiner2ID);
I am wanting to select the 3 biggest selling records with this is my table
CREATE TABLE IF NOT EXISTS `contas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_service` int(11) DEFAULT NULL,
`data` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=28 ;
the 'id_service' is the main column, the more sales, more records are added with the same 'id_service'.
so how do I do this without using PHP and select in descending order?
I tried this
select id_service, count(*) as id_service
from vendas WHERE id_service is not null
group by id_service order by id_service desc LIMIT 3
You have aliased both columns to the same name. No wonder the query is confused. Try this:
select id_service, count(*) as cnt
from vendas WHERE id_service is not null
group by id_service
order by cnt desc
LIMIT 3;
I am trying to make some sort of SQL Query where I only get the 10 people with the most referrals, but with minimum 1 referral.
My Table looks like this:
CREATE TABLE IF NOT EXISTS `beta_list` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`email` varchar(250) NOT NULL,
`referrer` int(10) NOT NULL,
`referral_code` int(10) NOT NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
I have tried something like this:
SELECT
email,
referral_code as refcode,
(SELECT COUNT(*) FROM beta_list WHERE referrer=refcode) as referrals
FROM
beta_list
WHERE
referrals > 0
ORDER BY
referrals DESC
LIMIT
10
But it just says "Unknown column 'referrals' in 'where clause'".
I am no sql guru, I am only just beginning to learn more complex sql queries, so any help on how to achieve something like this would be deeply appreciated!
Cheers!
Try this - Add an outer query to extract the results from inner query -
select ref.email, ref.refcode, ref.referrals from
(
SELECT
email,
referral_code as refcode,
(SELECT COUNT(*) FROM beta_list WHERE referrer=refcode) as referrals
FROM
beta_list
) as ref
WHERE
ref.referrals > 0
ORDER BY
ref.referrals DESC
LIMIT
10
Give this a go:
SELECT email,referral_code as refcode,count(*) as referrals
FROM beta_list
WHERE referrer = referral_code
GROUP BY email,referral_code
ORDER BY referrals DESC
LIMIT 10;