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
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;
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;
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.
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.
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);
?>