Mysql group by (custom number for result) - mysql

I have a table
Id Name Id_collection Price
1 good1 2 10
2 good2 1 101
3 good3 3 102
4 good4 2 10
5 good5 2 10
I need to Group By id_collection, but i need to show 2 rows (to be able to change this value via variable or ... Ex: to change to 3 or 4 )
not
1
2
3
Ex
1
1
2
2
3
3
or
Ex
1
1
1
2
2
2
3
3
3
so the result must be
Id Name Id_collection Price
1 good1 2 10
4 good4 2 10
2 good2 1 101
3 good3 3 102
I was thinking about procedure or loop, but i didn't that before, Please help!!!

You want to group by adjacent values . . . in MySQL. You can use variables to assign the group. Alternatively, you can use this method to assign the group: count the number of rows that have id_collection different from each row with a smaller id.
You don't specify how to calculate the other columns, but here is a guess:
select min(id) as id, min(name) as name, id_collection, avg(price) as price
from (select t.*,
(select count(*)
from t t2
where t2.id_collection <> t.id_collection and
t2.id < t.id
) as grp
from t
) t
group by id_collection, grp;
EDIT:
I just realized that you probably don't want to aggregate the results; you probably just want the first row. For that, use variables:
select t.*
from (select t.*,
(#rn := if(#id = id_collection, #rn,
if(#id := id_collection, #rn + 1, #rn + 1)
)
) as rn
from t cross join
(select #id = -1, #rn := 0) params
order by id
) t
where rn = 1;

I edited a little the answer which #Gordon_Linoff posted, and it works now, you just have to change rn <= 3 this number and will get the various results.
#Gordon_Linoff - Thank you, this really helped me
select t.*
from (select t.*,
(#rn := if(#id = id_collection, #rn + 1,
if(#id := id_collection, 1, 0)
)
) as rn
from t cross join
(select #id := -1, #rn := 0) params
order by id_collection
) t
where rn <= 3;

Related

Select recent n number of entries of all users from table

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;

Getting first N rows between 2 timestamps for each ID Mysql

I have table like below, start_t is my timestamp in unixtime for example (1438326239412) but for simplicity I wrote small numbers here:
user_id | start_t | duration
1 12 1
1 15 2
1 4 5
2 9 6
2 10 5
3 9 6
I want to get first N rows for each user_id between two time stamps. This is my code but it returns more than the limit I want:
SELECT us.* FROM (SELECT us.*, (#rn := if(#i = us.user_id, #rn + 1, if(#i := us.user_id, 1, 1) ) ) AS seqnum FROM user_stats us,tourn_user tu CROSS JOIN (SELECT #rn := 0, #i := -1) params WHERE (us.start_t+us.duration)<= 20 AND us.start_t >= 4 ORDER BY us.user_id, start_t ASC ) us WHERE seqnum <= 1
The result should look like this for that specific example:
user_id | start_t | duration
1 4 5
2 9 6
3 9 6
Here is the solution in case someone wants:
select us.* from (select us.*, (#rn := if(#i = us.user_id, #rn + 1, if(#i := us.user_id, 1, 1) ) ) as seqnum from user_stats us cross join (select #rn := 0, #i := -1) params where (us.start_t+us.duration)<= 15 AND us.start_t >= 0 order by us.user_id, start_t ASC ) us where seqnum <= 1

MySQL Return multiple rows if aggregated value is more than 1

Table has aggregated values but i need to return multiple rows if the value is greater than one.
Here is how the table looks now:
date description amount
1/1/2015 alpha 3
1/1/2015 beta 1
Here is how i need it to return:
date description amount
1/1/2015 alpha 1
1/1/2015 alpha 1
1/1/2015 alpha 1
1/1/2015 beta 1
Any help would be greatly appreciated.
You need a table of numbers. Something like this works for up to 3 and can be easily extended:
select t.date, t.description, 1 as amount
from table t join
(select 1 as n union all select 2 union all select 3) n
on n.n <= t.amount;
EDIT:
If you have enough rows in the table for the larger amounts, you can do:
select t.date, t.description, 1 as amount
from table t join
(select #rn := #rn + 1 as n
from table cross join (select #rn := 0) vars
) n
on n.n <= t.amount;
This worked perfectly.
select t.date, t.description, 1 as amount
from table t join
(select #rn := #rn + 1 as n
from table cross join (select #rn := 0) vars
) n
on n.n <= t.amount;

Insert a new column based on existing column values

I have a table table1 in mysql like this:
price item count
100 xyz 5
200 xyz 1
300 xyz 4
400 abc 1
500 abc 2
I want to insert a new column 'new_price' that will hold the 'price' for that 'item' with the highest 'count'. So the new table will be
price item count new_price
100 xyz 5 100
200 xyz 1 100
300 xyz 4 100
400 abc 1 500
500 abc 2 500
What is the most efficient way of doing this? Thanks very much for your help.
I think the easiest approach is to use variables:
select t.*,
(#price := if(#i = item, #price,
if(#i := item, price, price)
)
) as new_price
from table1 t cross join
(select #i := '', #price := -1) vars
order by item, count desc;
If you actually want to update values in the table, you can fit this into an update as well:
update table1 t join
(select t.*,
(#price := if(#i = item, #price,
if(#i := item, price, price)
)
) as new_price
from table1 t cross join
(select #i := '', #price := -1) vars
order by item, count desc
) tp
on tp.item = t.item and tp.price = t.price and tp.count = t.count
set t.new_price = tp.price;

How to get a running value per subgroup?

Suppose I have a table like this:
testdata
col1
1
2
3
1
1
2
3
...
how to arrive at a query that give also the the running number / unique id per subgroup ?
col1 | sub_id
1 1
2 1
3 1
1 2
1 3
2 2
3 2
... ...
Assuming you have a column that specifies the ordering, you can use a correlated subquery:
select col1,
(select count(*) from table t2 where t2.col1 = t.col1 and t2.id <= t.id) as sub_id
from table t;
You can also do this with variables:
select t.*,
(#rn := if(#id = id, #rn + 1,
if(#id := id, 1, 1)
)
) as sub_id
from table t cross join
(select #rn := 0, #id := -1) vars
order by col1, id;