Consecutive Occurrences of Values - google-bigquery

I have to conduct a count of CONSECUTIVE occurrences by date of student absences.
Within two categories: 2 consecutive fouls/absences and 3 or more consecutive fouls.
Data example:
Name
Date
Present
Student 1
2022/01/01
false
Student 1
2022/01/02
false
Student 1
2022/01/03
true
Student 1
2022/01/04
false
Student 1
2022/01/05
false
Student 1
2022/01/06
false
Student 1
2022/01/07
true
Student 1
2022/01/08
false
Student 1
2022/01/09
false
Student 1
2022/01/10
false
Student 1
2022/01/11
false
Student 1
2022/01/12
true
Student 1
2022/01/13
false
Student 1
2022/01/14
false
Student 1
2022/01/15
true
Expected outcome:
Students
Count 2 Consecutive Absences
Count 3 consecutives or more
Total of Absences
Student 1
2
2
11
I tried to conduct this code using LAG and OVER, but I wasn't successful.
CASE WHEN LAG(present) OVER (order by date) is false AND present is false THEN 1 END as test

Consider below approach
select name,
countif(absences = 2) as Count_2_Consecutive_Absences,
countif(absences > 2) as Count_3_or_more_Consecutive_Absences,
sum(absences) as Total_Absences,
from (
select name, countif(not present) absences
from (
select *, countif(new_grp) over(partition by name order by date) grp
from (
select *, ifnull(present != lag(present) over(partition by name order by date), true) new_grp
from your_table
)
)
group by name, grp
having absences > 0
)
group by name
if applied to sample data in your question - output is

This is a gaps and islands problem, and once approach uses the difference in row numbers method:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Date) rn1,
ROW_NUMBER() OVER (PARTITION BY Name, Present ORDER BY Date) rn2
FROM yourTable
),
cte2 AS (
SELECT Name,
COUNT(CASE WHEN Present = false THEN 1 END) AS num_consec_absent
FROM cte
GROUP BY Name, rn1 - rn2
)
SELECT Name,
COUNT(CASE WHEN num_consec_absent = 2
THEN 1 END) AS Count_2_Consecutive_Absences,
COUNT(CASE WHEN num_consec_absent > 2
THEN 1 END) AS Count_3_or_more_Consecutive_Absences,
SUM(num_consec_absent) AS Total_Absences
FROM cte2
GROUP BY Name;
Here is a running demo for SQL Server.

Related

Is there a way to partition by incremental series in Postgressql?

In postgressql is there a way to attain the result below by using partition by or any other way?
last_name year increment partition
Doe 2000 1 1
Doe 2001 2 1
Doe 2002 3 1
Doe 2003 -1 2
Doe 2004 1 3
Doe 2005 2 3
Doe 2006 3 3
Doe 2007 -1 4
Doe 2008 -2 4
SELECT last_name,
year,
increment,
SUM(CASE WHEN increment < 0 THEN 1 ELSE 0 END) OVER (PARTITION BY last_name ORDER BY year) AS partition
FROM your_table
ORDER BY last_name, year;
It seems that you want to group the consecutive positive/ negative values together, one option is to use a difference between two row_number functions, this will make the partition but with unordered group numbers.
select *,
row_number() over (partition by last_name order by year) -
row_number() over (partition by last_name,
case when increment>=0 then 1 else 2 end order by year) as prt
from tbl
order by last_name, year
If you want the partitions in order (1, 2, 3...) you could try another approach using lag and running sum as the following:
select last_name, year, increment,
1 + sum(case when sign(increment) <> sign(pre_inc) then 1 else 0 end) over
(partition by last_name order by year) as prt
from
(
select *,
lag(increment, 1 , increment) over
(partition by last_name order by year) pre_inc
from tbl
) t
order by last_name, year
See demo
If the increment column does encrease over the column year, it will be marked as 1; otherwise, it will be marked as 0. Then, we group the successive data using "LAG", regardless of whether the increment is positive or negative.
with cte as (
select * ,
row_number() over (partition by last_name order by year) as row_num,
case when increment >= LAG(increment,1,0) over (partition by last_name order by year)
then 1 else 0 end rank_num
from mytable
),
cte2 as (
select *, LAG(rank_num,1,1) over (partition by last_name order by year) as pre
from cte
order by year
)
select last_name, year, increment, 1+sum(case when pre <> rank_num then 1 else 0 end) over
(partition by last_name order by year) as partition
from cte2;

How to calculate the number of a day in series of consecutive dates?

I have a table
id name created_at
1 name 1 08/01/2017
2 name 2 08/02/2017
3 name 3 08/03/2017
4 name 4 08/05/2017
5 name 5 08/06/2017
6 name 6 08/07/2017
7 name 7 08/10/2017
8 name 8 08/12/2017
I need to add a column where be rank for all rows, but if they were created from day to day.
The result should be like below
id name created_at days_on
1 name 1 08/01/2017 1
2 name 2 08/02/2017 2
3 name 3 08/03/2017 3
4 name 4 08/05/2017 1
5 name 5 08/06/2017 2
6 name 6 08/07/2017 3
7 name 7 08/10/2017 null
8 name 8 08/12/2017 null
There are many answers describing typical approaches to similar problems, where you can also find an explanation of the techniques used below.
select
id, name, created_at,
case when count(*) over wa > 1 then row_number() over wo end as rank
from (
select
id, name, created_at,
sum(first) over w as part
from (
select *, (lag(created_at) over w+ 1 is distinct from created_at)::int as first
from my_table
window w as (order by id)
) s
window w as (order by id)
) s
window
wa as (partition by part),
wo as (partition by part order by id);
DbFiddle.
This is a variation of the group-and-islands problem. Let me show a solution using lag() to define the groups:
lag() to get the previous day
cumulative sum to get the groups
row_number() to assign the final values
This works as:
select id, name, created_at,
(case when count(*) over (partition by grp) > 1
then row_number() over (partition by grp order by id)
end) as days_on
from (select t.*,
sum( (prev_ca <> created_at - interval '1 day')::int ) as grp
from (select t.*,
lag(created_at) over (order by id) as prev_ca
from t
) t;

How to calculate unique rank in SQL Server (without any duplication)?

I want to calculate unique rankings but I get duplicate rankings
Here's my attempt:
SELECT
TG.EMPCODE,
DENSE_RANK() OVER (ORDER BY TS.COUNT_DEL DESC, TG.COUNT_TG DESC) AS YOUR_RANK
FROM
(SELECT
EmpCode,
SUM(CASE WHEN Tgenerate = 1 THEN 1 ELSE 0 END) AS COUNT_TG
FROM
TBLTGENERATE1
GROUP BY
EMPCODE) TG
INNER JOIN
(SELECT
EMP_CODE,
SUM(CASE WHEN STATUS = 'DELIVERED' THEN 1 ELSE 0 END) AS COUNT_DEL
FROM
TBLSTAT
GROUP BY
EMP_CODE) TS ON TG.EMPCODE = TS.EMP_CODE;
The output I get is like this:
EID Rank
---------
102 1
105 2
101 2
103 3
106 4
There is same rank for 105 and 101.
How do I calculate unique ranking?
Use ROW_NUMBER() instead of DENSE_RANK():
SELECT TG.EMPCODE,
ROW_NUMBER() OVER (ORDER BY TS.COUNT_DEL DESC, TG.COUNT_TG DESC) AS YOUR_RANK
Ties will then be given sequential rankings.

TSQL - If Valid is true for at least 1 user in each group then return that group as Valid?

Essentially I want to run a query that returns a list of all the DISTINCT Groups, and if Valid is true for at least 1 user in each group then return that group as true. Otherwise return that group as false. Many thanks
This is my table:
UserID | GroupID | Valid
------------------------
1 1 True
2 2 False
3 3 False
4 1 False
5 4 True
Results expected
UserID | GroupID | Valid
------------------------
1 1 True
2 2 False
3 3 False
5 4 True
You can using row_number():
select t.*
from (select t.*,
row_number() over (partition by groupid
order by (case when valid = 'True' then 1 else 2 end)
) as seqnum
from t
) t
where seqnum = 1;
You can do this count and min window functions. (assuming your SQL Server version supports them)
select first_user as userid,groupid,new_valid as valid
from (
select t.*
,min(userID) over(partition by groupid) as first_user
,case when count(case when valid='True' then 1 end) over(partition by groupid) > 0 then 'True' else 'False' end as new_valid
from tbl t
) t
where first_user=userID
If you need the first true or false userID per groupID, use
select first_user as userid,groupid,new_valid as valid
from (
select t.*
,case when count(case when valid='True' then 1 end) over(partition by groupid) > 0 then min(case when valid='True' then userID end) over(partition by groupid)
else min(userID) over(partition by groupid) end as first_user
,case when count(case when valid='True' then 1 end) over(partition by groupid) > 0 then 'True' else 'False' end as new_valid
from tbl t
) t
where first_user=userID
You Can do Something like this:
SELECT MAX(UserID),
GroupID,
Valid
FROM yourtable
WHERE Valid = 1
OR (Valid = 0 and GroupID not in (SELECT GroupID FROM yourtable WHERE Valid = 1))
Group By GroupID, Valid
order by MAX(UserID)
This way you will get the last UserID for one group. If want the first, just change MAX to MIN.

Count value from database

I have below data.
ID UserID Grade
1 1 A
2 1 A
3 1 a
4 1 a
5 1 b
6 1 C
7 1 c
8 1 b
9 2 b
10 2 C
11 1 b
12 2 A
I want below results.
RunningNumber UserID Result Count
1 1 a 4
2 1 b 1
3 1 c 2
4 1 b 2
5 2 b 1
6 2 c 1
7 2 a 1
The result will count column Grade (case insensitive) and must be group by UserID.
Use as below
SELECT UserID, Grade, COUNT(ID) AS Count
FROM Table1
GROUP BY UserID,Grade;
Updated
SELECT ROW_NUMBER()
OVER (ORDER BY Grade) AS RunningNumber ,
UserID, Grade, COUNT(ID) AS Count
FROM Table1
GROUP BY UserID,Grade;
Just use Group by & count
SELECT ID ,UserID,Grade
COUNT(ID) FROM Tabel
GROUP BY Grade,UserID;
You can use DENSE_RANK for the RunningNumber, COUNT(*)OVER for the count per ID and ROW_NUMBER to take only the first row:
WITH CTE AS
(
SELECT
RunningNumber = DENSE_RANK() OVER (ORDER BY UserID, Grade),
UserID, Result = Grade,
[Count] = COUNT(*) OVER (PARTITION BY UserID, Grade),
RowNum = ROW_NUMBER() OVER (PARTITION BY UserID, Grade ORDER BY ID)
FROM dbo.TableName
)
SELECT RunningNumber, UserID, Result, [Count]
FROM CTE
WHERE RowNum = 1
Demo
$query_not="SELECT count(status) AS sum FROM `user_leave_details`WHERE (status='2' Or status='3') AND user_id_no='$user_id_no'";
$result=mysqli_query($bd,$query_not);
while($arr=mysqli_fetch_array($result))
{
$sum=$arr['sum'];
}
connect.php
<?php
$mysql_hostname = "localhost";
$mysql_user = "root";
$mysql_password = "";
$mysql_database = "";
$bd=mysqli_connect($mysql_hostname,$mysql_user,$mysql_password,$mysql_database);
?>