How can I limit result per each id in WHERE clause?
My query is:
SELECT name
FROM location_areas
WHERE parent IN ("1,2,3")
ORDER BY popularity,name
Parent is not unique.
I need to get 10 results for each parent id in WHERE clause.
for example table structure is:
id name parent
1 name 0
2 name 1
3 name 1
4 name 80
5 name 80
6 name 80
7 name 80
8 name 1
Try this:
SELECT
T.name,
T.popularity,
T.parent,
T.rank
FROM
(
SELECT
L.name,
L.popularity,
L.parent,
#rank := IF(#parent = parent, #rank + 1, 1) rank,
#parent := parent
FROM location_areas L,
(SELECT #rank := 1, #parent := NULL) R
) T
WHERE T.rank <= 10
EDIT
SELECT T.name, T.popularity, T.parent, T.level, T.rank
FROM (
SELECT L.name, L.popularity,
L.parent, L.level,
#rank := IF(#parent = parent, #rank + 1, 1) rank,
#parent := parent
FROM location_areas L,
(SELECT #rank := 1, #parent := NULL) R
WHERE L.parent IN (".$ids.")
) T WHERE T.rank <= 10;
You can simply do it lie this
SET #level = 0;
SET #group = '';
SELECT
name
FROM (
SELECT
name ,
parent
#level := IF(#group = parent, #level+1, 1) AS level,
#group := parent as EGroup
FROM test
WHERE parent IN ("1,2,3")
ORDER BY parent
) rs
WHERE level < 11
Related
I'm trying to produce an SQL statement to SUM top 5 values within categories per userID to create an overall total. Is this possible and how do I achieve it? I can sum the top 5 per single category or ALL but struggling to see how I can SUM each separate category total together.
For example,
ID Cater Weight
--------------------------------
1 Cheese 10
2 Bacon 15
1 Cheese 5
2 Bacon 10
1 Cheese 22
2 Cheese 5
1 Bacon 10
1 Cheese 10
2 Cheese 5
1 Cheese 20
2 Bacon 10
1 Cheese 30
The results i'm looking for is,
ID Total_Weight
-------------------
1 102 Top 5 Cheese (10+22+10+20+30) + Top 5 Bacon (10)
2 45 Top 5 Cheese (5+5) + Top 5 Bacon (15+10+10)
Any values outside of the Top 5 are ignored.
The code below displays the SUM of the top 5 weights from ALL categories AS total weight. Can I achieve what I want from a single statement?
$log = "SELECT id, cater,
SUM(weight) AS total_weight
FROM ( SELECT id,
CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1
ELSE #ROW_NUMBER := 1
END AS rn,
cater,
weight,
#id := id
FROM individual,
(SELECT #ROW_NUMBER := 1, #ID := '') r
ORDER
BY
id, weight DESC
) TMP
WHERE rn <= 5
AND cater <> ''
GROUP
BY id
ORDER
BY total_weight DESC";
There might have some other better solution. But this will provide your expected result-
SELECT B.id,
SUM(T_weight) Total_Weight,
group_concat(concat('TOP 5 ',B.cater,' (',B.T,')') SEPARATOR ' ') Details
FROM
(
SELECT ID,cater,SUM(Weight) T_weight,group_concat(weight SEPARATOR '+') T
FROM
(
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cheese' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Bacon' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat3' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat4' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat5' ORDER BY id, weight DESC
)A WHERE rn < 6
UNION ALL
SELECT * FROM
(
SELECT id,cater, CASE WHEN #ID = ID THEN #ROW_NUMBER := #ROW_NUMBER + 1 ELSE #ROW_NUMBER := 1 END AS rn,weight,#id := id
FROM your_table,(SELECT #ROW_NUMBER := 1, #ID := ''
) r
WHERE cater = 'Cat6' ORDER BY id, weight DESC
)A WHERE rn < 6
)A
GROUP BY ID,Cater
)B
group by id
The output is-
1 191 TOP 5 Cheese (10+22+20+10+30) TOP 5 Cat3 (25+9+20+16+13) TOP 5 Bacon (10)
2 45 TOP 5 Cheese (5+5) TOP 5 Bacon (15+10+10)
If you have MySql version 8 or higher, you can use this code:
SELECT id, SUM (weight)
FROM (SELECT test.*,
ROW_NUMBER ()
OVER (PARTITION BY id, categ ORDER BY weight DESC)
rn
FROM test) sub
WHERE sub.rn < 6
GROUP BY id;
You need MySql 8+ because I used the function row_number and only from version 8 it exists.
In the below fiddle example, I used Microsoft Sql Server 2017 because they didn't had MySql 8 or higher.
You can see your example here.
I need to display progressive_total using mysql.
I have a table look like this:
id value cumulative_total
1 100 100
2 150 250
3 200 450
4 300 750
I want to add a new column called progressive_total, so the table would look like this:
id value Progressive_total cumulative_total
1 100 - 100
2 150 100 250
3 200 250 450
4 300 450 750
I use the below mysql query to display the Cumulative total:
SELECT t.id, t.value,
(SELECT SUM(x.value) FROM house_details x WHERE x.id <= t.id) AS cumulative_total
FROM house_details t
WHERE t.income IS NOT NULL AND t.income!= '' AND YEAR(t.cdate) = YEAR(CURRENT_DATE())
ORDER BY t.id
The above query works fine and getting cumulative_total. But i want to display progressive_total also, like i have given in the example.
SELECT t.id, t.value,
IFNULL((SELECT SUM(x.value) FROM house_details x WHERE x.id < t.id), '-') AS progressive_total,
(SELECT SUM(x.value) FROM house_details x WHERE x.id <= t.id) AS cumulative_total
FROM house_details t
WHERE t.income IS NOT NULL AND t.income!= '' AND YEAR(t.cdate) = YEAR(CURRENT_DATE())
ORDER BY t.id
You can use variables:
SELECT t.id, t.value,
#s := #s +IF(#temp := #prev,
IF(#prev := t.value, #temp, #temp),
IF(#prev := t.value, #temp, #temp)) AS progressive_total,
(SELECT SUM(x.value)
FROM house_details x
WHERE x.id <= t.id) AS cumulative_total
FROM house_details t
CROSS JOIN (SELECT #s := 0, #prev := 0, #temp := 0) AS v
WHERE t.income IS NOT NULL and t.income!= '' AND
YEAR(t.cdate) = YEAR(CURRENT_DATE())
order by t.id
Variable #prev is set to the value of the immediately preceding row. We have to use an intermediate variable like #temp, to hold the value of #prev, before #prev is set to its new value.
Demo here
Edit: The above query can be simplified to:
SELECT id, value,
#ps := #ps + prev AS progressive_total,
#cs := #cs + value AS cumulative_total
FROM (
SELECT t.id, t.value,
IF(#temp := #prev,
IF(#prev := t.value, #temp, #temp),
IF(#prev := t.value, #temp, #temp)) AS prev
FROM house_details t
CROSS JOIN (SELECT #s := 0, #prev := 0, #temp := 0) AS v
WHERE t.income IS NOT NULL and t.income!= '' AND
YEAR(t.cdate) = YEAR(CURRENT_DATE())
ORDER BY t.id) AS x
CROSS JOIN (SELECT #ps := 0, #cs := 0) AS u
ORDER BY id
Demo here
I have a below table and wants to select only last 2 entries of all users.
Source table:
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 10 2016-5-12
2 10 2016-5-12
1 11 2016-6-12
2 12 2016-8-12
3 12 2016-8-12
2 13 2016-8-12
1 14 2016-9-12
3 14 2016-9-12
3 11 2016-6-12
Expected output is like, (should list only recent 2 quizid entries for all users)
-------------------------------------
UserId | QuizId(AID)|quizendtime(AID)|
--------------------------------------
1 14 2016-9-12
1 11 2016-6-12
2 13 2016-8-12
2 12 2016-8-12
3 14 2016-9-12
3 12 2016-8-12
Any idea's to produce this output.
Using MySQL user defined variables you can accomplish this:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2
Working Demo
Note: If you want at most x number of entries for each user then change the condition in where clause like below:
WHERE t.row_number <= x
Explanation:
SELECT
*,
IF(#sameUser = UserId, #a := #a + 1 , #a := 1) row_number,
#sameUser := UserId
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC;
This query sorts all the data in ascending order of userId and descending order of quizendtime(AID).
Now take a walk on this (multi) sorted data.
Every time you see a new userId assign a row_number (1). If you see the same user again then just increase the row_number.
Finally filtering only those records which are having row_number <= 2 ensures the at most two latest entries for each user.
EDIT: As Gordon pointed out that the evaluation of expressions using user defined variables in mysql is not guaranteed to follow the same order always so based on that the above query is slightly modified:
SELECT
t.UserId,
t.`QuizId(AID)`,
t.`quizendtime(AID)`
FROM
(
SELECT
*,
IF (
#sameUser = UserId,
#a := #a + 1,
IF(#sameUser := UserId, #a := 1, #a:= 1)
)AS row_number
FROM your_table
CROSS JOIN (SELECT #a := 1, #sameUser := 0) var
ORDER BY UserId , `quizendtime(AID)` DESC
) AS t
WHERE t.row_number <= 2;
WORKING DEMO V2
User-defined variables are the key to the solution. But, it is very important to have all the variable assignments in a single expression. MySQL does not guarantee the order of evaluation of expressions in a select -- and, in fact, sometimes processes them in different orders.
select t.*
from (select t.*,
(#rn := if(#u = UserId, #rn + 1,
if(#u := UserId, 1, 1)
)
) as rn
from t cross join
(select #u := -1, #rn := 0) params
order by UserId, quizendtime desc
) t
where rn <= 2;
I have a query that loops through each result and updates a column:
SET #counter = 0;
UPDATE users SET rank_level = #counter := #counter + 1 ORDER BY level DESC;
SELECT rank_level, level FROM users ORDER BY rank_level ASC;
Which outputs:
But what I am trying to do is only increment the variable if the level value changes. So where the two rows that have the same level are, they would have the same rank of 8 too.
Any ideas? Thanks.
rank() is a bit tricky in MySQL. One way is with a correlated subquery:
select u.*,
(select count(*) + 1
from users u2
where u2.level < u.level
) as rank
from users u;
This is tricky to put into an update. Assuming you have a userId column, you can use join:
update users u join
(select u.*,
(select count(*) + 1
from users u2
where u2.level < u.level
) as rank
from users u
) ur
on u.userId = ur.userId
set rank_level = rank;
Doing a rank with variables is rather tricky (row_number() and dense_rank() are easier), because it requires three variables. Here is the select version:
select u.*,
(#r := if(#l = level,
if(#rn := #rn + 1, #r, #r)
if(#l := level,
#rn := #rn + 1, #rn := #rn + 1
)
)
) as ranking
from users u cross join
(select #l := -1, #rn : = 0, #r := 0) params
order by level;
So in the end I went with this:
SET #prev_value = NULL;
SET #rank_count = 0;
UPDATE users SET rank_level = CASE
WHEN #prev_value = level THEN #rank_count
WHEN #prev_value := level THEN #rank_count := #rank_count + 1
END
ORDER BY level DESC;
Based on this answer: Rank function in MySQL
I am trying to create a query that selects 2 random rows for each parent_id from my table.
At the moment my query always returns the first 2 id's for each parent_id. (For example: 1,2 of parent_id=1).
My table is currently as follows:
id, title , parent_id
1, Title1 , 1
2, Title2 , 1
3, Title3 , 1
4, Title4 , 2
5, Title5 , 2
6, Title6 , 2
7, Title7 , 2
8, Title8 , 3
9, Title9 , 3
10, Title10, 3
My current query is:
SELECT id,title,parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
meals.*
FROM meals CROSS JOIN
(SELECT #rn := 0, #parent_id := '') params
ORDER BY rand()
) meals
WHERE rn <= 2
ORDER BY id ASC
I would like my result to change on each query so for example one result will return the id's 1,3 for parent_id=1 and one will return 2,3 and so on...
Try moving the order by rand() clause into its own derived table
SELECT id,title,parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
t1.*
FROM ( SELECT * FROM meals CROSS JOIN
(SELECT #rn := 0, #parent_id := '') params
ORDER BY rand() ) t1
ORDER BY parent_id
) meals
WHERE rn <= 2
ORDER BY id ASC
http://sqlfiddle.com/#!9/3310f/1
I think you just need to add parent_id into the order by:
SELECT id, title, parent_id, rn
FROM (SELECT (#rn := if(#parent_id = parent_id, #rn + 1,
if(#parent_id := parent_id, 1, 1)
)
) as rn,
meals.*
FROM meals CROSS JOIN
(SELECT #rn := 0, #parent_id := -1) params
ORDER BY parent_id, rand()
) meals
WHERE rn <= 2
ORDER BY id ASC;
And, if parent_id is a number, there is no reason to make #parent_id a string.