Count Consecutive Numeric values in SQL - mysql

There are already many existing questions about this scenario, however I am unable to replicate the answers in my scenario.
I have a following sample Data Set:
ID Number | Values
754321 0
754321 0
754321 0
754321 0
754321 1
754321 0
754321 1
754321 0
754321 2
754321 0
754329 3
754329 4
754329 5
754329 6
754329 7
754329 8
754329 9
I want the SQL query that outputs the ID Number with the number of times the value of "0" appears consecutively. So, for the above table I would like to get the output as follows:
ID Number Count of Consecutive 0 Values
754321 4

This is a form of gaps-and-islands problem. You can assign each 0 a group by counting the number of non-zero values before it. Then aggregate.
However, SQL tables represent unordered sets. There is no ordering unless a column specifies the ordering. Let me assume you have one. Then:
select count(*)
from (select t.*,
sum(values <> 0) over (partition by idnumber order by <ordering col>) as grp
from t
) t
where values = 0
group by idnumber, grp;

If you're running a version of MySQL that doesn't support window functions, you can implement this functionality using variables:
SELECT `ID Number`, MAX(cnt) AS `Max Consecutive 0 Values`
FROM (SELECT `ID Number`, SUM(`Values` = 0) AS cnt
FROM (SELECT `ID Number`, `Values`,
#cnz:= CASE WHEN `Values` != 0 THEN #cnz + 1
ELSE #cnz
END AS cnz
FROM data
CROSS JOIN (SELECT #cnz := 0) init
ORDER BY date
) c
GROUP BY `ID Number`, cnz) s
GROUP BY `ID Number`
Output
ID Number Max Consecutive 0 Values
754321 4
754329 0
Demo on SQLFiddle

Related

Create histogram given start and end values in SQL

I have an SQL table with "start" and "end" columns: for the sake of simplicity, let's assume they are integers between 1 and 10. I would like to somehow obtain a histogram of the values between "start" and "end".
For instance, given the following rows:
start
end
3
8
4
9
I would like to obtain the following histogram:
time
count
1
0
2
0
3
1
4
2
5
2
6
2
7
2
8
2
9
1
10
0
I really have no idea where to start looking in the SQL syntax to get that result -- maybe an inner join?
You can use a recursive CTE to generate times -- if you don't have a handy tally or numbers table. Then join and aggregate:
with recursive cte as (
select 1 as t
union all
select t + 1
from cte
where t < 10
)
select cte.t,
(select count(*)
from t
where cte.t between t.start and t.end
) as cnt
from cte;
Here is a db<>fiddle.

Leave Histograms (Leave Pattern) generation in SQL

In the Data shown,we need to do a continuous pattern check of Leaves,
for eg:
CASE
WHEN count("Leaves") BETWEEN 1 AND 2 THEN '1-2'
WHEN count("Leaves") BETWEEN 3 AND 5 THEN '3-5'
WHEN count("Leaves") >5 THEN '>5'
ELSE 'Above 5' END AS "Leave Occurence",
On Jan 1st and 2nd employee has taken 2 leaves togather which belongs to
'1-2'Bucket,
similarly 8,9,10,11th JAN it is contineous for 4 Days hence in '3-4'Bucket
and contineously more than 5 Leaves belongs to [<5]Bucket
Now we need the count of each Bucket for Month wise.
Here 1-2Bucket is 2
3-4Bucket is 1
<5 Bucket is also 1
We used this code ,but it gives the SUM,But not checking the contineous pattern
Year Month Leaves
2011 1-Jan 1
2-Jan 1
3-Jan 0
4-Jan 0
5-Jan 0
6-Jan 0
7-Jan 0
8-Jan 1
9-Jan 1
10-Jan 1
11-Jan 1
12-Jan 0
13-Jan 0
14-Jan 0
15-Jan 1
16-Jan 1
17-Jan 1
18-Jan 1
19-Jan 1
20-Jan 1
21-Jan 0
22-Jan 0
23-Jan 1
24-Jan 1
You can identify each group of leaves by counting the number of non-leaves before it. Then you have aggregation:
select min(date), max(date), count(*) as numdays
from (select t.*,
(select count(*)
from t t2
where t2.date <= t.date and t2.leave = 0
) as grp
from t
where t.leave = 1
) t
group by grp;
You can then format the results however you like. This gives you one row per continuous "leave" period.

sql calculate percentage over grouped data

I have a table X like this,
student ans_status question_id
1 1 10
2 -1 10
3 1 10
4 0 10
1 -1 11
2 1 11
3 -1 11
4 -2 11
expected o/p is
10 2/3
11 1/3
etc..
Now, i want the data fro each question 10 like,
number of 1's/(total of 1's and -1's for each question)
I have tried this,
select (select count(student_id) from X
where question_id=10 and ans_status=1) / count(student_id)
from X
where question_id=10
group by ans_status
having ans_status in(1,-1).
i can do it in a nested query, by again selecting and grouping according to the status condition, but is there any way better to do this?
please note i want this for all questions in the table
You can just do:
select question_id,
avg(ans_status = 1)
from X
where ans_status in (1, -1)
group by question_id;
This uses the MySQL feature that a boolean expression is treated as an integer in a numeric context. "True" is 1 and "false" is 0, so the average turns out to be the percentage that are true.
If you want the values independently:
select question_id,
sum(ans_status = 1), count(*)
from X
where ans_status in (1, -1)
group by question_id;
Use GROUP BY for taking count of each question_id for getting count of answer_id is 1 or -1.
Query
select t.`question_id`,
t.`count_1` / t.`total_count` as `new_col` from(
select `question_id`,
sum(case `ans_status` when 1 then 1 when -1 then 1 else 0 end) as `count_1`,
count(*) as `total_count`
from `your_table_name`
group by `question_id`
)t;
Find a demo here

Count Totals by Day of Week and Week Total for each Subject

In my table I have recorded various bits of information but the key fields for this problem are the subject_id and lesson_time where I want to group by subject to give a total count of rows for each day of week for each subject and total rows for each subject(weekly total), so from this sample data:
id subject_id lesson_time
1 4 2015-04-28
2 4 2015-04-28
3 3 2015-04-28
4 1 2015-04-28
5 4 2015-04-27
I want to count the totals for each subject for monday to friday and week total, so output for the above data example would be:
subject_id monday_total tuesday_total wednesday_total ... week_total
1 0 1 0 1
3 0 1 0 1
4 1 2 0 3
I can get total by subject easy enough as it is just count(*) after group by, what I am struggling with is the count for each individual day, my current (non working) query is
SELECT
subject_id,
COUNT( DAYOFWEEK(lesson_time)=2) AS monday_total,
COUNT( DAYOFWEEK(lesson_time)=3) AS tuesday_total,
COUNT( DAYOFWEEK(lesson_time)=4) AS wednesday_total,
COUNT( DAYOFWEEK(lesson_time)=5) AS thursday_total,
COUNT( DAYOFWEEK(lesson_time)=6) AS friday_total,
COUNT(*) AS week_total
FROM
tbl_lessons
GROUP BY
subject_id
Any help would be much appreciated.
try this
SELECT
subject_id,
SUM(DAYOFWEEK(lesson_time)=2) AS monday_total,
SUM(DAYOFWEEK(lesson_time)=3) AS tuesday_total,
SUM(DAYOFWEEK(lesson_time)=4) AS wednesday_total,
SUM(DAYOFWEEK(lesson_time)=5) AS thursday_total,
SUM(DAYOFWEEK(lesson_time)=6) AS friday_total,
COUNT(*) AS week_total
FROM
tbl_lessons
GROUP BY
subject_id
In mysql COUNT(n) counts every row (+ 1 for every row where n is not NULL). SUM(n) sums all n values (+ n for every row).
As boolean expression returns 1 or 0 SUM(DAYOFWEEK(lesson_time)=2) will return number of rows where DAYOFWEEK(lesson_time)=2 (it's like summing booleans 1+0+0+1+1+1+0+0+1+...)
SELECT date(lesson_time) AS lesson_time, count( * ) AS count FROM tbl_lessons GROUP BY date(lesson_time)

MYSQL Count entries and group by two criteria with output in to a 2d table

I have a table that looks something like this;
(actual table is larger with several million rows)
Test_table
ID Day Value
=============
1 1 4
2 1 -1
3 1 27
4 1 3
5 1 -2
6 1 -5
7 1 3
8 1 1
9 1 1
10 1 Null
11 2 1
12 2 1
13 2 2
14 2 -1
15 2 -3
I want to produce a table of these two columns with the count of the number of times each entry appears, a 2d table with the day down the rows, and the values across the top with each cell containing the count of entries in that criteria like the below;
Desired output
Day Null -5 -3 -2 -1 1 2 3 4 27
==================================================================================
1 1 1 1 1 2 2 1 1
2 1 1 2 1
A query like;
select day, value, count(*) as count
from test_Table
group by day, value
Order by day asc, value desc
;
produces the data as many rows and only 3 columns... How can I get the desired output?
You can do this with conditional aggregation:
select day,
sum(value is NULL) as "NULL",
sum(value = -5) as "-5",
sum(value = -3) as "-3",
sum(value = -2) as "-2",
sum(value = -1) as "-1",
sum(value = 1) as "1",
sum(value = 2) as "2",
sum(value = 3) as "3",
sum(value = 4) as "4",
sum(value = 27) as "27"
from test_Table
group by day
Order by day asc;
Note two things. First, the column values are fixed. If you want dynamic column names, then you need to use dynamic SQL. Second, instead of blanks this will have 0 for the days with no count of a particular value.
The short answer is that it can't be done in MySQL.
The reason is that a SELECT statement has to specify the number of columns to be returned, a name and datatype for each column. And MySQL cannot dynamically generate columns to be returned for you.
The longer answer is that you would need a query of the form:
SELECT t.Day
, SUM(IF(t.value IS NULL,1,0)) AS `Null`
, SUM(IF(t.value = -5 ,1,0) AS `-5`
, SUM(IF(t.value = -3 ,1,0) AS `-3`
, ...
FROM mytable t
GROUP BY t.Day
with each column specified in the SELECT list.
One trick you can use is to use another, separate query to help write that query you need. This has to be a separate step, a separate query. To get the list of values you want returned as column headers would be of the form:
SELECT IFNULL(v.value,'Null') AS val
FROM mytable v
GROUP BY v.value
ORDER BY IF(v.value IS NULL,0,1), v.value
If you are doing this just in MySQL (and not an application), you can have MySQL help generate the required SQL text for you (using SQL to generate SQL)
SELECT CONCAT(' , SUM(IF(t.value',
IFNULL(v.value,' IS NULL',CONCAT(' = ',v.value)),
',1,0)) AS `',v.value,'`'
) AS expr
FROM mytable v
GROUP BY v.value
ORDER BY IF(v.value IS NULL,0,1), v.value
Then copy the string values returned from the expr column, paste those into an editor, and finish creating the SQL statement, like the example shown above.
The answer from Gordon shows the expression IF(col=12,1,0) can be abbreviated to col=12.
I always find myself typing that out the IF(conditional,valtrue,valfalse), but that's just the way my brain works. It's just easier for me to read.
Similarly the expression in the ORDER BY in my example...
ORDER BY IF(v.value IS NULL,0,1)
could be rewritten...
ORDER BY v.value IS NOT NULL