I have a MySQL query where I want to add a rank column.
SELECT AM.* , count(ALM.id) as likes FROM admin_models as AM
left join admin_liked_models as ALM on AM.id = ALM.admin_model_id
group by AM.id
admin_models and admin_liked_models has a one to many relationship.
The highest likes should be rank 1.
Can anyone point me out how to do this? Thanks!
Unfortunately MySQL doesn't support Row_Number() or other analytic functions. Here is one way to produce the same results though:
SELECT *,
#rn:=#rn+1 rn
FROM (
SELECT AM.* , count(ALM.id) as likes
FROM admin_models as AM
LEFT JOIN admin_liked_models as ALM ON AM.id = ALM.admin_model_id
GROUP BY AM.id
) t, (SELECT #rn:=0) t2
ORDER BY likes DESC
You probably don't need the subquery, but I'm just showing the separation.
this only works when using with ORDER BY is it not possible to make the rank column based on the number of likes column?
It looks you are not looking for rank. If you do not want to use ORDER and want to use the rank as some grade, you can try something like this:
SELECT AM.* , count(ALM.id) as likes,
((SELECT max(maxval) from
(SELECT count(ALM.id) as maxval FROM admin_models as AM
left join admin_liked_models as ALM on AM.id = ALM.admin_model_id
group by AM.id) as subq) - count(ALM.id) + 1) as rank
FROM admin_models as AM
left join admin_liked_models as ALM on AM.id = ALM.admin_model_id
group by AM.id
Please note that in this case the grade will not be sequential, and several models will have the same grade.
Related
I am trying to make a query to fetch the newest car for each user:
select * from users
left join
(select cars.* from cars
where cars.userid=users.userid
order by cars.year desc limit 1) as cars
on cars.userid=users.userid
It looks like it says Unknown column "users.userid" in where clause
I tried to remove cars.userid=users.userid part, but then it only fetches 1 newest car, and sticks it on to each user.
Is there any way to accomplish what I'm after? thanks!!
For this purpose, I usually use row_number():
select *
from users u left join
(select c.* , row_number() over (partition by c.userid order by c.year desc) as seqnum
from cars c
) c
on c.userid = u.userid and c.seqnum = 1;
One option is to filter the left join with a subquery:
select * -- better enumerate the columns here
from users u
left join cars c
on c.userid = u.userid
and c.year = (select max(c1.year) from cars c1 where c1.userid = c.userid)
For performance, consider an index on car(userid, year).
Note that this might return multiple cars per user if you have duplicate (userid, year) in cars. It would be better to have a real date rather than just the year.
Maybe there are better and more efficient way to query this. Here is my solution;
select users.userid, cars.*
from users
left join cars on cars.userid = users.userid
join (SELECT userid, MAX(year) AS maxDate
FROM cars
GROUP BY userid) as sub on cars.year = sub.maxDate;
I know that the question name is kinda confusing, but what I want to do is (if possible)the following: In the following MYSQL query, I can get all phone numbers from the first 15 people, what I need is that with this query, if the person has more than 3 phone numbers, only return the first 3.
SELECT distinct(pp.PhoneNumber)
FROM person p
INNER JOIN personPhone pp ON p.personId = pp.personId
WHERE !ISNULL(pp.PhoneNumber)
GROUP BY p.PersonId, pp.PhoneNumber
ORDER BY p.personId ASC LIMIT 0,15;
I have tried making a subquery on the SELECT, inside the distinct statement, but without any success
EDIT: Sorry I forgot to add the version of MySQL that I'm using, it is 5.7.24
You don't say what version of MySQL you have. If you have MySQL 8.x you can do:
select *
from (
select
personId, PhoneNumber,
row_number() over(partition by personId order by PhoneNumber) as rn
from (
SELECT distinct p.personId, pp.PhoneNumber
FROM person p
INNER JOIN personPhone pp ON p.personId = pp.personId
) x
) y
where rn <= 3
Another approach would be to use GROUP_CONCAT to get all the phone numbers per person and then use SUBSTRING_INDEX to make sure you fetch only up to 3.
SELECT p.PersonId ID, SUBSTRING_INDEX(GROUP_CONCAT(DISTINCT(pp.PhoneNumber)), ',', 3) Phone
FROM person p
INNER JOIN personPhone pp ON p.PersonId = pp.PersonId
WHERE pp.PhoneNumber IS NOT NULL
GROUP BY p.PersonId
ORDER BY p.PersonId ASC LIMIT 15;
Your query does not get all phone numbers from the first 15 people. It gets 15 phone numbers, which is probably for fewer than 15 people.
To limit to 15 people, use a subquery. Then you can limit to three numbers per person:
SELECT distinct(pp.PhoneNumber)
FROM (SELECT p.*
FROM person p
ORDER BY p.personId
LIMIT 15
) p15 INNER JOIN
personPhone pp
ON p.personId = pp.personId
WHERE pp.PhoneNumber IS NOT NULL AND
pp.PhoneNumber <= COALESCE( (SELECT pp2.PhoneNumber
FROM personPhone pp2
WHERE pp2.PersonId = pp.PersonId
ORDER BY pp2.PhoneNumber
OFFSET 2 LIMIT 1
), pp.PhoneNumber)
I'm looking for help using sum() in my SQL query:
SELECT links.id,
count(DISTINCT stats.id) as clicks,
count(DISTINCT conversions.id) as conversions,
sum(conversions.value) as conversion_value
FROM links
LEFT OUTER JOIN stats ON links.id = stats.parent_id
LEFT OUTER JOIN conversions ON links.id = conversions.link_id
GROUP BY links.id
ORDER BY links.created desc;
I use DISTINCT because I'm doing "group by" and this ensures the same row is not counted more than once.
The problem is that SUM(conversions.value) counts the "value" for each row more than once (due to the group by)
I basically want to do SUM(conversions.value) for each DISTINCT conversions.id.
Is that possible?
I may be wrong but from what I understand
conversions.id is the primary key of your table conversions
stats.id is the primary key of your table stats
Thus for each conversions.id you have at most one links.id impacted.
You request is a bit like doing the cartesian product of 2 sets :
[clicks]
SELECT *
FROM links
LEFT OUTER JOIN stats ON links.id = stats.parent_id
[conversions]
SELECT *
FROM links
LEFT OUTER JOIN conversions ON links.id = conversions.link_id
and for each link, you get sizeof([clicks]) x sizeof([conversions]) lines
As you noted the number of unique conversions in your request can be obtained via a
count(distinct conversions.id) = sizeof([conversions])
this distinct manages to remove all the [clicks] lines in the cartesian product
but clearly
sum(conversions.value) = sum([conversions].value) * sizeof([clicks])
In your case, since
count(*) = sizeof([clicks]) x sizeof([conversions])
count(*) = sizeof([clicks]) x count(distinct conversions.id)
you have
sizeof([clicks]) = count(*)/count(distinct conversions.id)
so I would test your request with
SELECT links.id,
count(DISTINCT stats.id) as clicks,
count(DISTINCT conversions.id) as conversions,
sum(conversions.value)*count(DISTINCT conversions.id)/count(*) as conversion_value
FROM links
LEFT OUTER JOIN stats ON links.id = stats.parent_id
LEFT OUTER JOIN conversions ON links.id = conversions.link_id
GROUP BY links.id
ORDER BY links.created desc;
Keep me posted !
Jerome
Jeromes solution is actually wrong and can produce incorrect results!!
sum(conversions.value)*count(DISTINCT conversions.id)/count(*) as conversion_value
let's assume the following table
conversions
id value
1 5
1 5
1 5
2 2
3 1
the correct sum of value for distinct ids would be 8.
Jerome's formula produces:
sum(conversions.value) = 18
count(distinct conversions.id) = 3
count(*) = 5
18*3/5 = 9.6 != 8
For an explanation of why you were seeing incorrect numbers, read this.
I think that Jerome has a handle on what is causing your error. Bryson's query would work, though having that subquery in the SELECT could be inefficient.
Use the following query:
SELECT links.id
, (
SELECT COUNT(*)
FROM stats
WHERE links.id = stats.parent_id
) AS clicks
, conversions.conversions
, conversions.conversion_value
FROM links
LEFT JOIN (
SELECT link_id
, COUNT(id) AS conversions
, SUM(conversions.value) AS conversion_value
FROM conversions
GROUP BY link_id
) AS conversions ON links.id = conversions.link_id
ORDER BY links.created DESC
I use a subquery to do this. It eliminates the problems with grouping.
So the query would be something like:
SELECT COUNT(DISTINCT conversions.id)
...
(SELECT SUM(conversions.value) FROM ....) AS Vals
How about something like this:
select l.id, count(s.id) clicks, count(c.id) clicks, sum(c.value) conversion_value
from (SELECT l.id id, l.created created,
s.id clicks,
c.id conversions,
max(c.value) conversion_value
FROM links l
LEFT JOIN stats s ON l.id = s.parent_id
LEFT JOIN conversions c ON l.id = c.link_id
GROUP BY l.id, l.created, s.id, c.id) t
order by t.created
This will do the trick, just divide the sum with the count of conversation id which are duplicate.
SELECT a.id,
a.clicks,
SUM(a.conversion_value/a.conversions) AS conversion_value,
a.conversions
FROM (SELECT links.id,
COUNT(DISTINCT stats.id) AS clicks,
COUNT(conversions.id) AS conversions,
SUM(conversions.value) AS conversion_value
FROM links
LEFT OUTER JOIN stats ON links.id = stats.parent_id
LEFT OUTER JOIN conversions ON links.id = conversions.link_id
GROUP BY conversions.id,links.id
ORDER BY links.created DESC) AS a
GROUP BY a.id
Select sum(x.value) as conversion_value,count(x.clicks),count(x.conversions)
FROM
(SELECT links.id,
count(DISTINCT stats.id) as clicks,
count(DISTINCT conversions.id) as conversions,
conversions.value,
FROM links
LEFT OUTER JOIN stats ON links.id = stats.parent_id
LEFT OUTER JOIN conversions ON links.id = conversions.link_id
GROUP BY conversions.id) x
GROUP BY x.id
ORDER BY x.created desc;
I believe this will give you the answer that you are looking for.
I have 3 tables:
user(id, name, id_school)
school(id, name)
result(id_user, stage1, stage2)
Now I would like to get school ranking - is sum of 2 columns: stage1 and stage2 of all users.
seems too simple:
select s.name as school, sum(stage1)+sum(stage2) as rank
from result r
join user u on u.id=r.id_user
join school s on s.id=u.id_school
group by s.id
and i really hope you have indexes.
Try this query. It is only assumption.please provide some data for testing
SELECT
s.id,
s.name AS SchoolName
(r.S1 + r.S2) AS Rank
FROM school as s
LEFT JOIN user as u ON u.id_school = s.id
LEFT JOIN (SELECT id_user , SUM(stage1) as S1 , SUM(stage2) FROM result GROUP BY id_user) as r ON r.id_user = u.id
GROUP BY s.id
ORDER BY Rank DESC
I would do something like this:
SELECT school.name, SUM(result.result1 + result.result2)
FROM school LEFT JOIN user ON (user.id_school = school.id) LEFT JOIN result ON (result.id_user = user.id) GROUP BY school.id
Hope it helps, good luck :)
I have comments table where everything is stored, and i have to SUM everything and add BEST ANSWER*10.
I need rank for whole list, and how to show rank for specified user/ID.
Here is the SQL:
SELECT m.member_id AS member_id,
(SUM(c.vote_value) + SUM(c.best)*10) AS total
FROM comments c
LEFT JOIN members m ON c.author_id = m.member_id
GROUP BY c.author_id
ORDER BY total DESC
LIMIT {$sql_start}, 20
How about something like this:
SET #rank=0;
SELECT * FROM (
SELECT #rank:=#rank+1 AS rank, m.member_id AS member_id,
(SUM(c.vote_value) + SUM(c.best)*10) AS total
FROM comments c
LEFT JOIN members m ON c.author_id = m.member_id
GROUP BY c.author_id
ORDER BY total DESC
) as sub
LIMIT {$sql_start}, 20
You may want to check out windowing functions if your MySQL version supports them...
SELECT m.member_id AS member_id,
(SUM(c.vote_value) + SUM(c.best)*10) AS total,
RANK() OVER (ORDER BY (SUM(c.vote_value) + SUM(c.best)*10)) as ranking
FROM comments c
LEFT JOIN members m ON c.author_id = m.member_id
GROUP BY c.author_id
ORDER BY total DESC;
Another possibility is this:
SELECT m.member_id AS member_id,
(SUM(c.vote_value) + SUM(c.best)*10) AS total,
(SELECT count(distinct <column you want to rank by>)
FROM comments c1
WHERE c1.author_id = m.member_id) as ranking
FROM comments c
LEFT JOIN members m ON c.author_id = m.member_id
GROUP BY c.author_id
ORDER BY total DESC;
NB: There's a lot of open questions around this, but the above two techniques are simple methods to determine rankings in general. You'll want to change the above to fit your exact need, as I'm a little fuzzy on what constitutes the rank for a member_id.
SELECT
#rank:=#rank+1 as rank,
m.member_id AS member_id,
(SUM(c.vote_value) + SUM(c.best)*10) AS total
FROM comments c,
(SELECT #rank:=0) as init
LEFT JOIN members m ON c.author_id = m.member_id
GROUP BY c.author_id
ORDER BY total DESC
LIMIT {$sql_start}, 20
In the solution, ranks are always increasing even if total is the same.