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;
Related
I have read a few similar questions on counting consecutive rows, but none of them gave me a clear answer. I hope someone could give me some help with my problem. I have the following table data:
ID TEST_VALUES
1 A
2 B
3 C
4 C
5 C
6 C
7 A
8 D
9 D
10 D
11 B
12 C
13 C
14 C
15 C
now I want to find three consecutive rows with the same value within the ID range is 1 to 10,such as when the ID range is 1 to 10,there has the 'C' continuous appear more than three times.
(note: this question has nothing to do with ID,The column ID is only the condition for my query,such as where ID > 1 and ID < 10)
you can try this one,
SELECT TEST_VALUES, MAX(cnt) AS maxCount
FROM (
SELECT TEST_VALUES, ID, COUNT(grp) AS cnt
FROM (
SELECT ID, TEST_VALUES, rn - rnByVal AS grp
FROM (
SELECT ID, TEST_VALUES,
#rn := #rn + 1 AS rn,
#rnByVal := IF (#val = TEST_VALUES,
IF (#val := TEST_VALUES, #rnByVal + 1, #rnByVal + 1),
IF (#val := TEST_VALUES, 1, 1)) AS rnByVal
FROM mytable
CROSS JOIN (SELECT #rn := 0, #rnByVal := 0, #val := '') AS vars
ORDER BY ID) AS t
) AS s
GROUP BY TEST_VALUES, grp ) AS u
GROUP BY TEST_VALUES
It will be return maximum count for occurring consecutive rows with the same value(more then 1).
I also think of a solution,hoping to help others
SELECT
TEST_VALUES
FROM(
SELECT
m.TEST_VALUES AS TEST_VALUES,
IF(#b = m.TEST_VALUES, #a := #a +1, #a := 0) AS countNUM,
#b := m.TEST_VALUES
FROM tableName m
JOIN (
SELECT
#a := 0
) AS t
) AS TEMP
WHERE countNUM >= 2
GROUP BY TEST_VALUES
SELECT
GROUP_CONCAT(`TEST_VALUES` ORDER BY `id` ASC) AS g
FROM
`tbl`
WHERE
`ID` >=1 AND `ID` <= 10
HAVING
g LIKE '%C,C,C,C%'
Ordering the GROUP_CONCAT() by id ensures the results are sequential. The HAVING clause extracts only those rows which contain at least four sequential instances of C.
Convoluted, but tested and working:
SELECT t1.ID AS t1_id, t1.TEST_VALUES AS t1_values,
(SELECT ID FROM test WHERE test.ID > t1.ID LIMIT 1 ) AS t2_id, (SELECT TEST_VALUES FROM test WHERE test.ID > t1.ID LIMIT 1 ) AS t2_values,
(SELECT ID FROM test WHERE test.ID > (SELECT ID FROM test WHERE test.ID > t1.ID LIMIT 1 ) LIMIT 1 ) AS t3_id, (SELECT TEST_VALUES FROM test WHERE test.ID > (SELECT ID FROM test WHERE test.ID > t1.ID LIMIT 1 ) LIMIT 1 ) AS t3_values,
(SELECT ID FROM test WHERE test.ID > (SELECT ID FROM test WHERE test.ID > (SELECT ID FROM test WHERE test.ID > t1.ID LIMIT 1 ) LIMIT 1 ) LIMIT 1 ) AS t4_id, (SELECT TEST_VALUES FROM test WHERE test.ID > (SELECT ID FROM test WHERE test.ID > (SELECT ID FROM test WHERE test.ID > t1.ID LIMIT 1 ) LIMIT 1 ) LIMIT 1 ) AS t4_values
FROM test AS t1
HAVING (
ID BETWEEN 1 AND 10 AND t2_id BETWEEN 1 AND 10 AND t3_id BETWEEN 1 AND 10 AND t4_id BETWEEN 1 AND 10
AND
t1_values = t2_values AND t2_values = t3_values AND t3_values = t4_values
)
Gives these results:
+-------+-----------+-------+-----------+-------+-----------+-------+-----------+
| t1_id | t1_values | t2_id | t2_values | t3_id | t3_values | t4_id | t4_values |
+-------+-----------+-------+-----------+-------+-----------+-------+-----------+
| 3 | C | 4 | C | 5 | C | 6 | C |
+-------+-----------+-------+-----------+-------+-----------+-------+-----------+
1 row in set (0.03 sec)
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;
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'm trying to select first row then skip X next rows then select rest in one query. For example if I have (a,b,c,d,e) in table I need to select "a" (first row) then skip X=2 rows ("b", "c") and then select rest which is "d" and "e", all in one query. So the result would be a,d,e
Try
select *
from
(
select *, #rank := #rank + 1 as rank
from your_table
cross join (select #rank := 0) r
order by colA
) tmp
where rank = 1
or rank > 3
or
select * from your_table
order by colA
limit 1
union all
select * from your_table
order by colA
limit 4, 9999999
You can use a variable to generate a row number:
select
YourField,
YourOtherField
from
(
select id,
YourField,
YourOtherField,
#row := #row + 1 as rownum
from YourTable
cross join (select #row:=0) c
order by YourField -- The field you want to sort by when you say 'first' and 'fourth'
) d
where
rownum = 1 or rownum >= 4
id value
---------
1 a
2 b
3 c
4 a
5 t
6 y
7 a
I want to select all rows where the value is 'a' and the row before it
id value
---------
1 a
3 c
4 a
6 y
7 a
I looked into
but I want to get all such rows in one query.
Please help me start
Thank you
I think the easiest way might be to use variables:
select t.*
from (select t.*,
(rn := if(value = 'a', 1, #rn + 1) as rn
from table t cross join
(select #rn := 0) params
order by id desc
) t
where rn in (1, 2)
order by id;
An alternative method uses a correlated subquery to get the previous value and then uses this in the where clause:
select t.*
from (select t.*,
(select t2.value
from table t2
where t2.id < t.id
order by t2.id desc
limit 1
) as prev_value
from table t
) t
where value = 'a' or prev_value = 'a';
With an index on id, this might even be faster than the method using variables.