Get distinct values from groups in MySQL - mysql

I want to get the id of the lowest points from each team (the team field).
My query works but i need to make sure the following query is good enough with a large table.
I need Simplification and Optimization.
Query:
SELECT T.id from teams as T
INNER JOIN (
SELECT MIN(T1.points) AS P FROM teams AS T1
GROUP BY T1.team LIMIT 5
) TJOIN ON T.points IN (TJOIN.P)
GROUP BY T.team
ORDER BY T.points ASC LIMIT 5
Table teams
id
team (foreign_key)
points (indexed)
1
a
100
2
a
101
3
b
106
4
c
105
5
c
102
Result
id
1
5
3

I believe the query you are looking for is:
SELECT MIN(T.id)
FROM teams as T
INNER JOIN (
SELECT team, MIN(points) AS min_points
FROM teams
GROUP BY team LIMIT 5
) TJOIN
ON T.team = TJOIN.team
AND T.points = TJOIN.min_points
GROUP BY T.team
ORDER BY T.points ASC
LIMIT 5
You need to join based on both the column being grouped by and the min value. Consider the result of your query if multiple teams had a score of 100.
Another way of doing this is to use ROW_NUMBER():
SELECT id
FROM (
SELECT id, points, ROW_NUMBER() OVER (PARTITION BY team ORDER BY points ASC, id ASC) rn
FROM teams
) t
WHERE rn = 1
ORDER BY points ASC
LIMIT 5

Related

How to display two tables side by side that are joined?

I'm currently learning MySQL and am working on a query that displays the top 5 and bottom 5 categories and groups by joining 2 tables. What I have meets the requirements but I want to display it more cleanly. I've got this to display by using a union but was wondering if I could show the results as four columns instead for a cleaner look. 2 columns related to the top 5 and 2 related to the bottom five categories determined by the number of groups in each category.
Current query:
SELECT*
FROM(SELECT
category_name,
count(category_name) AS NumOfGroups
From
category c
JOIN
grp g ON c.category_id=g.category_id
GROUP BY category_name
order by NumOfGroups desc
LIMIT 5) most
UNION
SELECT *
FROM (SELECT
category_name,
count(category_name) AS NumOfGroups
From
category c
JOIN
grp g ON c.category_id=g.category_id
GROUP BY category_name
ORDER BY NumOfGroups ASC
LIMIT 5) Least;
This displays:
category NumOfGroups
Tech 911
Food & Drink 790
Photography 320
Outdoors & Adventure 218
Games 166
Singles 4
Fitness 15
Paranormal 16
Fashion & Beauty 26
Movements & Politics 32
Can I take this one step further to display a result like below?
Would I have to transpose?
Desired result:
category NumOfGroups category NumOfGroups
Tech 911 Singles 4
Food & Drink 790 Fitness 15
Photography 320 Paranormal 16
Outdoors & Adventure 218 Fashion & Beauty 26
Games 166 Movements & Politics 32
Create a CTE where you use ROW_NUMBER() window function twice to rank the rows based on the value of NumOfGroups and then do a self join:
WITH cte AS (
SELECT c.category_name, COUNT(*) NumOfGroups,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) rn_most,
ROW_NUMBER() OVER (ORDER BY COUNT(*)) rn_least
FROM category c JOIN grp g
ON c.category_id = g.category_id
GROUP BY c.category_name
)
SELECT c1.category_name category_most, c1.NumOfGroups NumOfGroups_most,
c2.category_name category_least, c2.NumOfGroups NumOfGroups_least
FROM cte c1 INNER JOIN cte c2
ON c2.rn_least = c1.rn_most
WHERE c1.rn_most <= 5
ORDER BY c2.rn_least
IMO, this is best done at the application level rather than in your database queries. Using each tool as it's designed results in cleaner solutions. However, if you really need to do this in mysql, you can generate row numbers in each of your subqueries and join them to make a unified result.
set #row:=0;
set #row2:=0;
SELECT most.category_name,most.members,least.category_name,least.members
FROM (
SELECT *,#row := #row + 1 as rownum
FROM (
SELECT
category_name,
count(*) numberOfGroups,
FROM category c
JOIN grp g ON c.category_id=g.category_id
GROUP by category_name
ORDER BY numberOfGroups DESC
LIMIT 5
) temp
) most
LEFT JOIN (
SELECT *,#row2 := #row2 + 1 as rownum
FROM (
SELECT
category_name,
count(*) numberOfGroups
FROM category c
JOIN grp g ON c.category_id=g.category_id
GROUP by category_name
ORDER BY numberOfGroups ASC
LIMIT 5
) temp
) least
ON most.rownum=least.rownum;
There's still a caveat where the "most" subquery needs to always be >= the number of row results relative to "least" or you'll get clipping. As long as it's always 5 though (as it appears to be very likely in your case), you'll be safe.

mysql query with group by and order

Table structure:
country
season
points
Current query:
SELECT SUM(points) AS total, country
FROM table
WHERE season >= X
GROUP BY country
ORDER BY total desc
This gives me a nice list ordered by a total points collected by a given country. BUT, if a country is tied with another country, I want to sort by their points in the latest season given, is that possible within the same query? And if so, how? (Remember its grouped at the moment)
example of rows
denmark (country), 1 (season), 10 (points)
denmark (country), 2 (season), 5 (points)
sweden(country), 1 (season), 5 (points)
sweden (country), 2 (season), 10 (points)
Maybe this'll do your trick:
SELECT SUM(points) AS total, country
FROM table
WHERE season >= X
GROUP BY country
ORDER BY total DESC, (SELECT t2.points FROM table t2 WHERE table.country=t2.country ORDER BY t2.season LIMIT 1) DESC
SELECT grp.total, grp.country
FROM
( SELECT SUM(points) AS total, country, MAX(season) AS max_season
FROM table
WHERE season >= X
GROUP BY country
) AS grp
LEFT JOIN
table AS t
ON t.country = grp.country
AND t.season = LATEST_SEASON
ORDER BY
grp.total DESC
, t.points DESC ;

What's the most efficient way to generate this report?

Given a table (daily_sales) with say 100k rows of the following data/columns:
id rep sales date
1 a 123 12/15/2011
2 b 153 12/15/2011
3 a 11 12/14/2011
4 a 300 12/13/2011
5 a 120 12/12/2011
6 b 161 11/15/2011
7 a 3 11/14/2011
8 c 13 11/14/2011
9 c 44 11/13/2011
What would be the most efficient way to write a report (completely in SQL) showing the two most recent entries (rep, sales, date) for each name, so the output would be:
a 123 12/15/2011
a 11 12/14/2011
b 153 12/15/2011
b 161 11/15/2011
c 13 11/14/2011
c 44 11/13/2011
Thanks!
FYI, your example is using mostly reserved words and makes it horrid for us to attempt to program against. If you've got the real table columns, gives those to us. This is postgres:
select name,value, max(date)
from the_table_name_you_neglect_to_give_us
group by 1,2
That'll give you a list of first name,value,max(date)...though I gotta ask why give us a column called value if it doesn't change in the example?
Lets say you do have an id column...we'll be consistent with your scheme and call it 'ID'...
select b.id from
(select name,value, max(date) date
from the_table_name_you_neglect_to_give_us
group by 1,2) a
inner join the_table_name_you_neglect_to_give_us b on a.name=b.name and a.value=b.value and a.date = b.date
This gives a list of all ID's that are the max...put it together:
select name,value, max(date)
from the_table_name_you_neglect_to_give_us
group by 1,2
union all
select name,value, max(date)
from the_table_name_you_neglect_to_give_us
where id not in
(select b.id from
(select name,value, max(date) date
from the_table_name_you_neglect_to_give_us
group by 1,2) a
inner join the_table_name_you_neglect_to_give_us b on a.name=b.name and a.value=b.value and a.date = b.date)
Hoping my syntax is right...should be close at any rate. I'd put a bracket around that entire thing then select * from (above query) order by name...gives you the order you want.
For MySQL, explained in #Quassnoi's blog, an index on (name, date) and using this:
SELECT t.*
FROM (
SELECT name,
COALESCE(
(
SELECT date
FROM tableX ti
WHERE ti.name = dto.name
ORDER BY
ti.name, ti.date DESC
LIMIT 1
OFFSET 1 --- this is set to 2-1
), CAST('1000-01-01' AS DATE)) AS mdate
FROM (
SELECT DISTINCT name
FROM tableX dt
) dto
) tg
, tableX t
WHERE t.name >= tg.name
AND t.name <= tg.name
AND t.date >= tg.mdate
If I understand what you mean.. Then this MIGHT be helpful:
SELECT main.name, main.value, main.date
FROM tablename AS main
LEFT OUTER JOIN tablename AS ctr
ON main.name = ctr.rname
AND main.date <= ctr.rdate
GROUP BY main.name, main.date
HAVING COUNT(*) <= 2
ORDER BY main.name ASC, main.date DESC
I know the SQL is shorter than the other posts, but just give it a try first..

Get User Rank Based on Sum with Pagination

There are several rank posts out there but I have yet to see one dealing with when the results are paginated and when the ranking criteria (in this case: points) is equal to the previous user. I have tried a few of the pre-existing examples but none have worked.
I have a table called "users" with the column "id". I also have a table called "points" with the columns "user_id" and "amount".
I need:
1.) Users with duplicate sum of points to have the same rank
Points Table
user_id amount
1 10
2 20
1 5
3 20
3 -5
4 5
Rank should be
rank user_id total
1 2 20
2 1 15
2 3 15
3 4 5
2.) Needs to maintain the ranking from one page to another so the rank has to be gathered in the query and not the resulting PHP.
3.) Display ALL users not just ones with rows in the points table because some users have 0 points and I want to display them last.
Right now I'm just listing the users in order of their points but their rank is not gathered because it wasn't working.
$getfanspoints = mysql_query("SELECT DISTINCT id,
(SELECT SUM(amount) AS points FROM points WHERE points.user_id = users.id) AS points
FROM users
ORDER BY points DESC LIMIT $offset, $fans_limit", $conn);
I've read these solutions and none have worked.
[Roland's Blog][1]
[How to get rank based on SUM's][2]
[MySQL, get users rank][3]
[How to get rank using mysql query][4]
and a few others whose link I can't find right now.
Any suggestions?
[EDIT]
I used ypercube's bottom answer.
SELECT COUNT(*) AS rank
, t.user_id
, t.total
FROM
( SELECT user_id
, SUM(amount) AS total
FROM points
GROUP BY user_id
) AS t
JOIN
( SELECT DISTINCT
SUM(amount) AS total
FROM points
GROUP BY user_id
) AS dt
ON
t.total <= dt.total
GROUP BY t.user_id
ORDER BY rank
, user_id
But the above may be really slow with a big table and points awarded often. It might be really better to have just this and calculate the ranks in your application code:
SELECT users.id AS user_id
, SUM(amount) AS total
FROM
users
LEFT JOIN
points
ON points.user_id = users.id
GROUP BY users.id
ORDER BY total DESC
, user_id
This will work, too (edited, to work with the users table and with OFFSET):
SELECT *
FROM
( SELECT
#rank := #rank + (#t <> total) AS rank
, user_id
, #t := total AS total
FROM
( SELECT users.id AS user_id
, COALESCE(SUM(amount),0) AS total
FROM users
LEFT JOIN points
ON users.id = points.user_id
GROUP BY users.id
) AS o
CROSS JOIN
( SELECT #rank := 0, #t := -999999
) AS dummy
ORDER BY total DESC
, user_id
) tmp
LIMIT x OFFSET y

Choose company with most records mysql

I have a mysql database with records from different companies. I need to select records from companies which have the most, second most and third most records and plot their number of records per year. How do I select them?
Many thanks.
EDIT:
The table would look something like this:
Company Year
A 1999
A 1999
B 1999
C 1999
A 2000
C 2000
A 2003
So if I select the company with the most records, A has the most records, and the output is;
Year Total
1999 2
2000 1
2003 1
And for the company with second most records, the output is ( in this case, company C)
Year Total
1999 1
2000 1
Third most will be company B.
I'd say something like
SELECT company,COUNT(company) AS rec,year
FROM your_table GROUP BY company, year ORDER BY rec DESC LIMIT 3;
most_frequent:
SELECT year, COUNT(year) AS total FROM your_table
WHERE company =
(SELECT company, COUNT(company) AS c
FROM your_table
GROUP BY c
ORDER BY c DESC
LIMIT 0,1)
second most:
SELECT year, COUNT(year) AS total FROM your_table
WHERE company =
(SELECT company, COUNT(company) AS c
FROM your_table
GROUP BY c
ORDER BY c DESC
LIMIT 1,1)
third most:
SELECT year, COUNT(year) AS total FROM your_table
WHERE company =
(SELECT company, COUNT(company) AS c
FROM your_table
GROUP BY c
ORDER BY c DESC
LIMIT 2,1)
In order to get two dimensional output in one query:
SELECT company, year, COUNT(year) AS total FROM your_table
INNER JOIN
(SELECT company, COUNT(company) AS c
FROM your_table
GROUP BY c
ORDER BY c DESC) AS t1
ON your_table.company = t1.company
ORDER BY t1.c DESC
I'm sorry, I haven't tested it. Just leave a comment if you have any trouble.