mysql: group functions on subqueries with limits - mysql

I have a group of users who perform tasks on which they are scored. I'm trying to create a report showing the average of each user's last 50 tasks.
user table: userid, username, usertype
task table: taskid, score, tasktype, userid
If I do:
SELECT u.userid, u.username, (SELECT AVG(score)
FROM task t
WHERE t.userid = u.userid AND t.tasktype = 'task1'
ORDER BY t.taskid DESC LIMIT 50) AS avgscore
FROM user u
WHERE u.usertype = 'utype';
that doesn't work because it does the limit 50 after it does the average of everything.
What I need is this:
SELECT u.userid, u.username, AVG(SELECT t.score
FROM task t
WHERE t.userid = u.userid AND t.tasktype = 'task1'
ORDER BY t.taskid DESC LIMIT 50) AS avgscore
FROM user u
WHERE u.usertype = 'utype';
but that is not valid syntax
I've tried sub-sub queries, but can't get it that way either, as I always get a problem with the limit, or a join, or unknown fields when I reference u.userid in the sub-subquery.
Is there a way to do this?

Use a subquery within the subquery:
SELECT u.userid, u.username,
(SELECT AVG(score)
FROM (select t.*
from task t
WHERE t.userid = u.userid AND t.tasktype = 'task1'
ORDER BY t.taskid DESC
LIMIT 50
) t
) AS avgscore
FROM user u
WHERE u.usertype = 'utype';
EDIT:
I didn't realize that MySQL would not recognize the u.userid. It should according to ANSI rules for the scoping of table aliases.
You can take a different approach which is find the 50th taskid value, and then take everything above that:
select ut.userid, ut.username, avg(t.score)
from (SELECT u.userid, u.username,
(SELECT substring_index(substring_index(group_concat(taskid order by taskid desc
), ',', 50), ',', -1)
from task t
WHERE t.userid = u.userid AND t.tasktype = 'task1'
) + 0 as taskid50
FROM user u
WHERE u.usertype = 'utype'
) ut join
task t
on ut.userid = t.userid and
ut.taskid50 >= t.taskid and t.tasktype = 'task1'
group by ut.userid, ut.username;

try this
SELECT u.userid, u.username, AVG(t.score ) AS avgscore
FROM user u
INNER JOIN task t
ON t.userid = u.userid
WHERE u.usertype = 'utype' AND t.tasktype = 'task1'
GROUP BY u.userid
ORDER BY t.taskid DESC LIMIT 50;

Related

MySQL GROUP BY and sort the group by column

I want to sort a group by a specific column.
I tried the following:
SELECT u.level, max(u.score),n.nick
FROM database.userdata u, database.nicks n
WHERE n.user = u.id
GROUP BY level
But this only prints the max score, and not the username for this score..
How can I fix this?
Thanks!
Try this way:
SELECT n.nick, u1.score, u1.level
FROM database.nicks AS n
JOIN database.userdata AS u1 ON n.user = u1.id
JOIN (
SELECT level, max(score) AS score
FROM database.userdata
GROUP BY level
) AS u2 ON u1.level = u2.level AND u1.score = u2.score

SQL - Select discint AND count from JOIN query

I have below query:
SELECT u.*
(SELECT sum(trs.amount)
FROM transactions trs
WHERE u.id = trs.user AND trs.type = 'Recycle' AND
trs.TIME >= UNIX_TIMESTAMP(CURDATE())
) as amt
FROM (SELECT DISTINCT user_by
FROM xeon_users_rented
) AS xur JOIN
users u
ON xur.user_by = u.username
LIMIT 50
Which selects some data from my database. The above query works fine. However, I would like to also select count(*) from xeon_users_rented where user_by = u.username This is what I have attempted:
SELECT u.*
(SELECT sum(trs.amount)
FROM transactions trs
WHERE u.id = trs.user AND trs.type = 'Recycle' AND
trs.TIME >= UNIX_TIMESTAMP(CURDATE())
) as amt,
(SELECT DISTINCT count(*)
FROM xeon_users_rented
WHERE xur.user_by = u.username
) AS ttl
FROM (SELECT DISTINCT user_by
FROM xeon_users_rented
) AS xur JOIN
users u
ON xur.user_by = u.username
LIMIT 50
However, that gives me the total number of rows in xeon_users_rented as ttl - not the total distinct rows where username = user_by
I think you can do what you want just by tinkering with your subquery a little. That is, change the select distinct to a group by:
SELECT u.*, xur.cnt,
(SELECT sum(trs.amount)
FROM transactions trs
WHERE u.id = trs.user AND trs.type = 'Recycle' AND
trs.TIME >= UNIX_TIMESTAMP(CURDATE())
) as amt
FROM (SELECT user_by, COUNT(*) as cnt
FROM xeon_users_rented
GROUP BY user_by
) xur JOIN
users u
ON xur.user_by = u.username
LIMIT 50;
Some notes:
SELECT DISTINCT is not really necessary, because you can do the same logic using GROUP BY. So, it is more important to understand GROUP BY.
You are using LIMIT with no ORDER BY. That means that you can get a different set of rows each time you run the query. Bad practice.

SQL with 2 INNER JOIN's on the same column not working

I am trying to use the following code to get the 6 users with which the current user has most recently chatted. I have two problems. First of all, if the current user has recieved a message from the other user but has only sent, that other user isnt fetched. Second of all, the ORDER BY clause is causing an error. Im a beginner in SQL so I have no idea what's going on.
Thanks in Advance!
Here's the code:
SELECT users.*
FROM users INNER JOIN
messages fromuser
ON (fromuser.fromid = users.id) INNER JOIN
messages touser
ON (touser.toid = users.id)
WHERE fromuser.toid = :userid OR touser.fromid = :meid
GROUP BY users.id
ORDER BY MAX(messages.datetime)
LIMIT 6;
This should do your job, and it relies less on MySQL extensions than your other answer so far. I estimate that it would perform about the same, but it's surely wordier.
SELECT u.*
FROM (
SELECT DISTINCT otherid
FROM (
SELECT
m.fromid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.toid = :userid
GROUP BY m.fromid
UNION ALL
SELECT
m.toid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.fromid = :userid
GROUP BY m.toid
) um
ORDER BY maxts DESC
LIMIT 6
) otheru
INNER JOIN users u
ON u.id = otheru.otherid
Your logic is doomed to fail, because one users.id cannot be two different values at the same time. I think this query does what you want:
SELECT u.*
FROM messages m INNER JOIN
users u
ON (m.fromid = u.id AND m.toid = :userid) OR
(m.toid = u.id AND m.fromid = :userid )
GROUP BY u.id
ORDER BY MAX(m.datetime) DESC
LIMIT 6;
Notice that it joins to the users table by the id that is not the current user.

MySQL INNER JOIN select only one row from second table

I have a users table and a payments table, for each user, those of which have payments, may have multiple associated payments in the payments table. I would like to select all users who have payments, but only select their latest payment. I'm trying this SQL but i've never tried nested SQL statements before so I want to know what i'm doing wrong. Appreciate the help
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*
FROM payments AS p
ORDER BY date DESC
LIMIT 1
)
ON p.user_id = u.id
WHERE u.package = 1
You need to have a subquery to get their latest date per user ID.
SELECT u.*, p.*
FROM users u
INNER JOIN payments p
ON u.id = p.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON p.user_ID = b.user_ID AND
p.date = b.maxDate
WHERE u.package = 1
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.id = (
SELECT id
FROM payments AS p2
WHERE p2.user_id = u.id
ORDER BY date DESC
LIMIT 1
)
Or
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
WHERE NOT EXISTS (
SELECT 1
FROM payments AS p2
WHERE
p2.user_id = p.user_id AND
(p2.date > p.date OR (p2.date = p.date AND p2.id > p.id))
)
These solutions are better than the accepted answer because they work correctly when there are multiple payments with same user and date. You can try on SQL Fiddle.
SELECT u.*, p.*, max(p.date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
ORDER BY p.date DESC
Check out this sqlfiddle
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM payments AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
ORDER BY p.user_id ASC, date DESC)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
You can try this:
SELECT u.*, p.*
FROM users AS u LEFT JOIN (
SELECT *, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY [Date] DESC) AS RowNo
FROM payments
) AS p ON u.userid = p.userid AND p.RowNo=1
There are two problems with your query:
Every table and subquery needs a name, so you have to name the subquery INNER JOIN (SELECT ...) AS p ON ....
The subquery as you have it only returns one row period, but you actually want one row for each user. For that you need one query to get the max date and then self-join back to get the whole row.
Assuming there are no ties for payments.date, try:
SELECT u.*, p.*
FROM (
SELECT MAX(p.date) AS date, p.user_id
FROM payments AS p
GROUP BY p.user_id
) AS latestP
INNER JOIN users AS u ON latestP.user_id = u.id
INNER JOIN payments AS p ON p.user_id = u.id AND p.date = latestP.date
WHERE u.package = 1
#John Woo's answer helped me solve a similar problem. I've improved upon his answer by setting the correct ordering as well. This has worked for me:
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN (
SELECT user_ID, MAX(date) as maxDate FROM
(
SELECT user_ID, date
FROM payments
ORDER BY date DESC
) d
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
WHERE a.package = 1
I'm not sure how efficient this is, though.
SELECT U.*, V.* FROM users AS U
INNER JOIN (SELECT *
FROM payments
WHERE id IN (
SELECT MAX(id)
FROM payments
GROUP BY user_id
)) AS V ON U.id = V.user_id
This will get it working
Matei Mihai given a simple and efficient solution but it will not work until put a MAX(date) in SELECT part so this query will become:
SELECT u.*, p.*, max(date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
And order by will not make any difference in grouping but it can order the final result provided by group by. I tried it and it worked for me.
My answer directly inspired from #valex very usefull, if you need several cols in the ORDER BY clause.
SELECT u.*
FROM users AS u
INNER JOIN (
SELECT p.*,
#num := if(#id = user_id, #num + 1, 1) as row_number,
#id := user_id as tmp
FROM (SELECT * FROM payments ORDER BY p.user_id ASC, date DESC) AS p,
(SELECT #num := 0) x,
(SELECT #id := 0) y
)
ON (p.user_id = u.id) and (p.row_number=1)
WHERE u.package = 1
This is quite simple do The inner join and then group by user_id and use max aggregate function in payment_id assuming your table being user and payment query can be
SELECT user.id, max(payment.id)
FROM user INNER JOIN payment ON (user.id = payment.user_id)
GROUP BY user.id
If you do not have to return the payment from the query you can do this with distinct, like:
SELECT DISTINCT u.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
This will return only users which have at least one record associated in payment table (because of inner join), and if user have multiple payments, will be returned only once (because of distinct), but the payment itself won't be returned, if you need the payment to be returned from the query, you can use for example subquery as other proposed.

Error with a join query

SELECT r.game, u.username, r.userid, r.points, u.format
FROM ".TBL_RANKING." r
INNER JOIN ".TBL_USERS." u
ON u.id = r.userid
WHERE r.type = '1' AND r.game =
(SELECT name
FROM ".TBL_GAME."
WHERE active = '1'
ORDER BY rand()
LIMIT 1)
AND u.format =
(SELECT name
FROM ".TBL_FORMAT."
WHERE active = '1'
ORDER BY rand() LIMIT 1)
ORDER BY r.points DESC LIMIT 5
This query isn't working as it's supposed to. It's selecting the odd user and sometimes none at all.
The query should:
-select a random game from the game table
-select a random format from the format table
-select users ranked on that game but only from the format selected
so if the random selection was FIFA 12 Xbox 360, it would find all users that are from format type Xbox 360 and are ranked on FIFA 12.
The table structure is as follows:
*tbl_ranking*
id
userid
game
points
type
*tbl_users*
id
username
format
*tbl_game*
id
name
*tbl_format*
id
name
Can anyone see a problem here?
try to have sub queries using left join
SELECT r.game, u.username, r.userid, r.points, u.format
FROM TBL_RANKING r
INNER JOIN TBL_USERS u
ON u.id = r.userid
LEFT JOIN (SELECT name FROM ".TBL_GAME." WHERE active = '1' ORDER BY rand() LIMIT 1) temp1
ON r.game=temp1.name
LEFT JOIN (SELECT name FROM ".TBL_FORMAT." WHERE active = '1' ORDER BY rand() LIMIT 1) temp2
ON u.format=temp2.name
WHERE r.type = '1'
AND temp1.name != ''
AND temp2.name != ''
ORDER BY r.points DESC LIMIT 5