Using MySQL, I am trying to find the highest number of consecutive rows in a table based on a value. For the sake of simplicity, my table looks like this:
+----+-------+
| ID | VALUE |
+----+-------+
| 1 | A |
| 2 | B |
| 3 | A |
| 4 | A |
| 5 | B |
| 6 | B |
| 7 | A |
| 8 | A |
| 9 | A |
| 10 | B |
+----+-------+
In this example, if I wanted the highest number of consecutive rows for 'A', I would get 3. For 'B', I would get 2. Even returning a result set of the counts of consecutive rows for 'A' would be preferable. I am newer to SQL so hints would be appreciated too. Any suggestions?
You can do it using variables:
SELECT VALUE, MAX(cnt) AS maxCount
FROM (
SELECT VALUE, COUNT(grp) AS cnt
FROM (
SELECT ID, VALUE, rn - rnByVal AS grp
FROM (
SELECT ID, VALUE,
#rn := #rn + 1 AS rn,
#rnByVal := IF (#val = VALUE,
IF (#val := VALUE, #rnByVal + 1, #rnByVal + 1),
IF (#val := VALUE, 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 VALUE, grp ) AS u
GROUP BY VALUE
Variables #rn and #rnByVal are used in order to simulate ROW_NUMBER window function, currently not available in MySQL. The second variable (#rnByVal) performs a count over VALUE partitions.
Using #rn - #rnByVal in an outer query we can calculate grp field, which identifies islands of consecutive rows having the same VALUE. Performing a GROUP BY on VALUE, grp we can calculate the population of these islands and, finally, in the outermost query, get the max population per VALUE.
Demo here
Related
I have a table with products.
The table has a companyId field.
Let's describe it like this:
id --- companyId
1 | 2
2 | 3
3 | 4
4 | 2
5 | 3
6 | 1
7 | 4
I want to select all the records ordered by companyId but with the company id looping, as so:
id --- companyId
6 | 1
1 | 2
2 | 3
3 | 4
4 | 2
5 | 3
7 | 4
How can I achieve it?
You can achieve this using MySQL user defined variables
SELECT
t.id,
t.companyId
FROM
(
SELECT
*,
IF(#sameCompany = companyId , #rn := #rn + 1,
IF(#sameCompany := companyId, #rn := 1,#rn := 1)
) AS rn
FROM companytable
CROSS JOIN (SELECT #sameCompany := -1, #rn := 1) AS var
ORDER BY companyId
) AS t
ORDER BY t.rn , t.companyId
See Demo
Explanation:
First sort the data according to companyId so that the same company ids stick together.
Now take a walk along this sorted result and assign a sequentially increasing row number every time you see the same companyId otherwise assign 1 as row number.
Now name this sorted result (with row number) t.
Finally sort these data (t) according to ascending row number and ascending companyId.
I have a table in the database that has among others 2 columns named item and slot. I want to create another column (call it cid) that is filled with numbers so that:
two rows that have the same item and same slot always have the same cid
rows with different item may have the same cid
the amount of distinct cid values is minimal
rows with the same item but different slot need to have different cid
If possible I'd like to just run an sql query that does that.
edit by request:
| item | slot | what cid should be
| a | x | 1
| a | y | 2
| a | y | 2
| a | z | 3
| b | x | 1
| b | y | 2
| b | q | 3
| c | x | 1
You can do what you want just by enumerating the slots for each item:
select t.*,
(#cid := if(#i = item, if(#s = slot, #cid, if(#s := slot, #cid + 1, #cid + 1))
if(#i := item and #s := 0, 1, 1)
)
) as cid
from t cross join
(select #i := -1, #cid := 0, #s := 0) param
order by item;
query
SELECT
(SELECT NAME FROM product_component) AS pcNAME,
(SELECT PROJECT_NAME FROM jira_project) AS jpNAME,
(SELECT FILTER_NAME FROM jira_filter) AS jfNAME
Each SELECT will return an indeterminate number of rows. I get the error Subquery returns more than 1 row. My desired output will be something like this (quick sketch):
=======================================
| pcNAME | jpNAME | jfNAME |
=======================================
| data | data | data |
+------------+------------+-----------+
| data | data | data |
+------------+------------+-----------+
| data | data | data |
+------------+------------+-----------+
| | data | data |
+------------+------------+-----------+
| | data | data |
+------------+------------+-----------+
| | data | |
+------------+------------+-----------+
Each column may produce a different number of rows than the others. So I will want to produce the amount of rows from the max and then blank out the others that don't fill the max number of rows.
NOTE: None of these tables have a shared column so cannot achieve as INNER JOIN
Any ideas on how this can be achieved?
One way to handle this in MySQL to use to variables, union all and aggregation:
SELECT MAX(NAME) as NAME, MAX(PROJECT_NAME) as PROJECT_NAME,
MAX(FILTER_NAME) as FILTER_NAME
FROM ((SELECT (#rnpc := #rnpc + 1) as rn, NAME, NULL as PROJECT_NAME, NULL as FILTER_NAME
FROM product_component CROSS JOIN
(SELECT #rnpc := 0) params
) UNION ALL
(SELECT (#rnpn := #rnpn + 1) as rn, NULL, PROJECT_NAME, NULL as FILTER_NAME
FROM jira_project CROSS JOIN
(SELECT #rnpn := 0) params
) UNION ALL
(SELECT (#rnf := #rnf + 1) as rn, NAME, NULL as PROJECT_NAME, NULL as FILTER_NAME
FROM jira_filter CROSS JOIN
(SELECT #rnf := 0) params
)
) t
GROUP BY rn
ORDER BY rn;
I have a problem with my Query. I want to select data from my Ranking Query.
My query output is Perfect Like:
------------------------------
Rank | ID | Username | Value
-------------------------------
1 | 5 | Julian | 5000
2 | 2 | Masha | 2400
3 | 4 | Misha | 2300
4 | 1 | Jackson | 1900
5 | 9 | Beruang | 400
-------------------------------
But when I select ID = 4, the output like this:
------------------------------
Rank | ID | Username | Value
-------------------------------
***1*** | 4 | Misha | 2300
-------------------------------
The output of ranking is 1, not 3.
My Query is :
SELECT #curRank := #curRank + 1 AS rank,
a.id, a.username
FROM partimer a CROSS JOIN
(SELECT #curRank := 0) vars
# WHERE a.id = 4
ORDER By id;
If Rank is dinamically computed, you could do this:
SELECT *
FROM (
SELECT #curRank := #curRank + 1 AS rank
, a.id
, a.username
FROM partimer a
CROSS JOIN (SELECT #curRank := 0) vars
ORDER BY value
) p
WHERE p.id = 4;
This way, you store temporary table with rank, and then select from it.
you should like this
SELECT *
FROM (
...{your Query}...
) qry
WHERE qry.id = 4
Your rank is calculated dynamically in the query. The issue here is that these dynamic calculations are applied after the where clause. In other words, when MySQL executes your query, it first filters out all the rows that adhere to the where clause, and only then applies the rank calculation. In the given query, the row with id=4 is indeed the 1st row between all the rows that adhere to the where clause.
One way to get your desired behavior is to perform the original query first and only then filter the results by using this query as a subquery and applying the where clause to the outer query:
SELECT *
FROM (SELECT #curRank := #curRank + 1 AS rank, a.id, a.username
FROM partimer a
CROSS JOIN (SELECT #curRank := 0) vars
ORDER By id) t
WHERE id = 4
I have a table like:
time | status
1390836600 | 1
1390836605 | 1
1390836610 | 0
1390836615 | 0
1390836620 | 1
1390836625 | 1
1390836630 | 1
I need to output the data "grouped" by the status, and sorted by time. The trick is that I need the groupings in chunks for each time the status changes, with the fields: MIN(time), status
So for the example data above I'd need an output like
MIN(time) | status
1390836600 | 1
1390836610 | 0
1390836620 | 1
This is not the behaviour of GROUP BY, which would just group ALL rows with the same status and only output 2 rows. But is something like this possible?
This (grouping of continuous ranges) is called gaps-and-islands problem and can be effectively solved by using analytic functions (specifically ROW_NUMBER()) which MySQL still has no support for.
But you can emulate ROW_NUMBER() with session variables in the following way
SELECT MIN(time) time, status
FROM
(
SELECT time, status,
#n := #n + 1 rnum,
#g := IF(status = #s, #g + 1, 1) rnum2,
#s := status
FROM table1 CROSS JOIN (SELECT #n := 0, #g := 0, #s := NULL) i
ORDER BY time
) q
GROUP BY rnum - rnum2
Output:
| TIME | STATUS |
|------------|--------|
| 1390836600 | 1 |
| 1390836610 | 0 |
| 1390836620 | 1 |
Here is a SQLFiddle demo