Sum Non Null Values Block in SQL - sql

How to add Non Null Values block by block without any grouping criteria :
Example input :
Machine Value DateTime
a null 1 Dec 2021 8:34AM
a 2 1 Dec 2021 8:35AM
a 1 1 Dec 2021 9:34AM
a 3 1 Dec 2021 10:11AM
a null 1 Dec 2021 11:14AM
a null 1 Dec 2021 11:16AM
a 5 1 Dec 2021 11:58AM
a 6 1 Dec 2021 11:59AM
Example output :
Machine Value DateTime SumValue
a null 1 Dec 2021 8:34AM
a 2 1 Dec 2021 8:35AM
a 1 1 Dec 2021 9:34AM
a 3 1 Dec 2021 10:11AM 6
a null 1 Dec 2021 11:14AM
a null 1 Dec 2021 11:16AM
a 5 1 Dec 2021 11:58AM
a 6 1 Dec 2021 11:59AM 11
I don't have any other grouping criteria other than device column , but I want sum block wise

You need to define the groups and use windowed SUM():
Table:
SELECT *
INTO Data
FROM (VALUES
('2021-12-12T09:00:01', 'a', null),
('2021-12-12T09:00:02', 'a', 2),
('2021-12-12T09:00:03', 'a', 1),
('2021-12-12T09:00:04', 'a', 3),
('2021-12-12T09:00:05', 'a', null),
('2021-12-12T09:00:06', 'a', null),
('2021-12-12T09:00:07', 'a', 5),
('2021-12-12T09:00:08', 'a', 6)
) v (Date, Machine, Value)
Statement:
SELECT
Date, Machine, Value,
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY Machine, GroupNumber ORDER BY Date DESC) = 1
THEN SUM(Value) OVER (PARTITION BY Machine, GroupNumber ORDER BY (SELECT NULL))
END AS SumValue
FROM (
SELECT
*,
SUM(CASE WHEN Value IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Machine ORDER BY Date) AS GroupNumber
FROM Data
) t
ORDER BY Machine, Date
Result:
Date Machine Value SumValue
2021-12-12T09:00:01 a
2021-12-12T09:00:02 a 2
2021-12-12T09:00:03 a 1
2021-12-12T09:00:04 a 3 6
2021-12-12T09:00:05 a
2021-12-12T09:00:06 a
2021-12-12T09:00:07 a 5
2021-12-12T09:00:08 a 6 11

Related

Counts and divide from two different selects with dates

I have a table with this kind of structure (Sample only)
ID | STATUS | DATE |
--- -------- ------
1 OPEN 31-01-2022
2 CLOSE 15-11-2021
3 CLOSE 21-10-2021
4 OPEN 11-10-2021
5 OPEN 28-09-2021
I would like to know the counts of close vs open records by week. So it will be count(close)/count(open) where close.week = open.week
If there are no matching values, need to return 0 of course.
I got to this query below
SELECT *
FROM
(SELECT COUNT(*) AS 'CLOSE', DATEPART(WEEK, DATE) AS 'WEEKSA', DATEPART(YEAR, DATE) AS 'YEARA' FROM TABLE
WHERE STATUS IN ('CLOSE')
GROUP BY DATEPART(WEEK, DATE),DATEPART(YEAR, DATE)) TMPA
FULL OUTER JOIN
(SELECT COUNT(*) AS 'OPEN', DATEPART(WEEK, DATE) AS 'WEEKSB', DATEPART(YEAR, DATE) AS 'YEARB' FROM TABLE
WHERE STATUS IN ('OPEN')
GROUP BY DATEPART(WEEK, DATE),DATEPART(YEAR, DATE)) TMPB
ON TMPA.WEEKSA = TMPB.WEEKSB AND TMPA.YEARA = TMPB.YEARB
My results are as below (sample only)
close | weeksa | yeara | open | weeksb | yearb |
------ -------- ------ ------- ------- ------
3 2 2021
1 3 2021
1 4 2021
2 20 2021 2 20 2021
7 22 2021
2 23 2021
7 26 2021
7 27 2021
2 28 2021 14 28 2021
2 29 2021
10 30
24 31 2021
2 32 2021 5 32
4 33 2021
1 34 2021 13 34 2021
6 35 2021
1 36 2021
1 38 2021
1 39 2021
2 41 2021
4 43 2021
1 45 2021
2 46 2021 25 46 2021
1 47 2021 5 47 2021
4 48 2021
1 49 2021 20 49 2021
1 50 2021 17 50 2021
1 51 2021
How do I do the math now?
If I do another select the query fails. So I guess either syntax is bad or the whole concept is wrong.
The required result should look like this (Sample)
WEEK | YEAR | RATIO |
----- ------ -------
2 2021 0
3 2021 0
4 2021 0
5 2021 0.93
20 2021 0.1
22 2021 0
23 2021 0
26 2021 0
1 2022 0.75
2 2022 0.23
4 2022 0.07
Cheers!
I have added some test data to check the logic, adding the same in the code.
;with cte as(
select 1 ID, 'OPEN' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 10 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 11 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 12 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 22 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 32 ID, 'CLOSE' as STATUS, cast('2021 -01-31' as DATE) DATE
union select 2,'CLOSE',cast('2021-11-28' as DATE)
union select 3,'CLOSE',cast('2021-10-21' as DATE)
union select 8,'CLOSE',cast('2021-10-21' as DATE)
union select 9,'CLOSE',cast('2021-10-21' as DATE)
union select 4,'OPEN', cast('2021-10-11' as DATE)
union select 5,'CLOSE', cast('2021-09-28' as DATE)
union select 6,'OPEN', cast('2021-09-27' as DATE)
union select 7,'CLOSE', cast('2021-09-26' as DATE) )
, cte2 as (
select DATEPART(WEEK,date) as week_number,* from cte)
,cte3 as(
select week_number,year(date) yr,count(case when status = 'open' then 1 end)open_count,count(case when status <> 'open' then 1 end) close_count from cte2 group by week_number,year(date))
select week_number as week,yr as year,
cast(case when open_count = 0 then 1.0 else open_count end /
case when close_count = 0 then 1.0 else close_count end as numeric(3,2)) as ratio
from cte3

Daily status using prior value as backfill

This is the input table (tbl_statuslog):
User_id
isactive
date
1
1
1 Feb 2021
2
1
1 Feb 2021
3
1
2 Feb 2021
2
0
5 Feb 2021
4
1
10 Feb 2021
4
0
10 Feb 2021
3
0
12 Feb 2021
create table tbl_statuslog
(
[user_id] int,
[isactive] bit,
[date] datetime
);
insert into tbl_statuslog (user_id, isactive, date) values
(1, 1, ' 1 Feb 2021'),
(2, 1, ' 1 Feb 2021'),
(3, 1, ' 2 Feb 2021'),
(2, 0, ' 5 Feb 2021'),
(4, 1, '10 Feb 2021'),
(4, 0, '10 Feb 2021'),
(3, 0, '12 Feb 2021');
I want to return output, given today is 16 Feb:
User_id
isactive
date
1
1
1 Feb 2021
.
.
.
1
1
16 Feb 2021
2
1
1 Feb 2021
.
.
2
1
4 Feb 2021
2
0
5 Feb 2021
.
.
2
0
16 Feb 2021
3
1
2 Feb 2021
.
.
3
1
11 Feb 2021
3
0
12 Feb 2021
.
.
3
0
16 Feb 2021
4
0
10 Feb 2021
.
.
4
0
16 Feb 2021
I have used following SQL to get the list of all dates.
DECLARE #StartDateTime DATETIME
DECLARE #EndDateTime DATETIME
SET #StartDateTime = '2021-02-01'
SET #EndDateTime = GETDATE();
WITH DateRange(DateData) AS
(
SELECT #StartDateTime as Date
UNION ALL
SELECT DATEADD(d,1,DateData)
FROM DateRange
WHERE DateData < #EndDateTime
)
SELECT DateData
FROM DateRange
OPTION (MAXRECURSION 0)
GO
Now I am thinking of doing a left join of this table with tbl_statuslog table. Thus I have a date irrespective of whether date exists in tbl_statuslog or not.
Then I want to backfill the isactive value for the date and user_id based on the previous value.
Can I use window function- example partition by user_id, order by date to achieve the result?
I'm blocked here because when evaluating the isactive value for a date and userid, how can I get access to prior 1 day value, or value of 2 days prior (and so on) when the previous day doesn't have value?
This answers the original version of the question.
I would suggest using recursion but only for each row:
with ts as (
select ts.*,
lead(date) over (partition by user_id order by date) as next_date
from tbl_statuslog ts
),
cte as (
select user_id, date, isactive,
coalesce(dateadd(day, -1, next_date), convert(date, getdate())) as end_date
from ts
union all
select user_id, dateadd(day, 1, date), isactive, end_date
from cte
where date < end_date
)
select user_id, date, isactive
from cte;
Here is a db<>fiddle.

how to create column based on other column in sql?

id year
1 2017
1 2018
1 2019
2 2018
2 2019
3 2017
3 2019
8 2017
4 2018
4 2019
I need to create column based on id and year column:
if a id present in 2017 and 2018 (subsequent year) then mark 'P' against 2017.
if a id present in 2018 and 2019 then mark 'P' then mark 'P' against 2017.
if a id present in 2017 but not in subsequent year then mark 'N' against 2017
If there is no data of subsequent year then mark 'N' in the previous year (2019)
output :
id year mark
1 2017 P
1 2018 P
1 2019 N
2 2018 P
2 2019 N
3 2017 N
3 2019 N
8 2017 P
4 2018 P
4 2019 N
You can try Lead() function. but please check output for Id = 8. Ideally it should be 'N'
SELECT *
,CASE WHEN LEAD(Year) OVER (PARTITION BY ID ORDER BY YEAR) - YEAR = 1 THEN 'P' ELSE 'N' END
FROM #Table
Hmmm . . . I'm thinking to generate flags for each year and then apply the logic. Based on the rules you describe:
select t.*,
(case when year in (2017, 2018) and
flag_2017 > 0 and flag_2018 > 0
then 'P'
when year in (2017) and
flag_2018 > 0 and flag_2019 > 0
then 'P'
else 'N'
end) as mark
from (select t.*,
sum(case when year = 2017 then 1 else 0 end) over (partition by id) as flag_2017,
sum(case when year = 2018 then 1 else 0 end) over (partition by id) as flag_2018,
sum(case when year = 2019 then 1 else 0 end) over (partition by id) as flag_2019
from t
) t;
Your sample results don't seem to follow your rules, but some simple variation on this appears to be what you want.
You don't need a physical column. What you want can be expressed as a query, using exists() or lead()
the LEAD() version:
\i tmp.sql
CREATE TABLE years(id integer, zyear integer);
INSERT INTO years (id , zyear ) VALUES
(1, 2017) , (1, 2018) , (1, 2019)
, (2, 2018) , (2, 2019) , (3, 2017)
, (3, 2019) , (8, 2017)
, (4, 2018) , (4, 2019)
;
SELECT id, zyear
, CASE when yy.nxt=yy.zyear+1 THEN 'P' ELSE 'N' END AS flagged
FROM (
SELECT id, zyear
, lead(zyear) OVER (partition by id ORDER BY zyear) AS nxt
FROM years
) yy
;
or, the EXISTS()-version:
SELECT id, zyear
, CASE when yy.xx THEN 'P' ELSE 'N' END AS flagged
FROM (
SELECT id, zyear
, EXISTS ( select * FROM years x where x.id=y.id and x.zyear = y.zyear+1) AS xx
FROM years y
) yy
;
Result: (the same for both versions)
psql:tmp.sql:2: NOTICE: drop cascades to table tmp.years
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 10
id | zyear | flagged
----+-------+---------
1 | 2017 | P
1 | 2018 | P
1 | 2019 | N
2 | 2018 | P
2 | 2019 | N
3 | 2017 | N
3 | 2019 | N
4 | 2018 | P
4 | 2019 | N
8 | 2017 | N
(10 rows)

I want cummulative row for a given input

I have table like below
Months cnt
Jan 2
Feb 3
Mar 5
I want output like below
Months cnt
Jan 2
Feb 2
Feb 3
Mar 2
Mar 3
Mar 5
I tried using below query but not getting the required output
Select distinct months, cnt, level
from (select months, cnt, rownum row_cnt
from tablename)
connect by level <= row_cnt
Order by months, cnt, level
Here's one option which converts month's names into their ordinal number (1 for Jan, 2 for Feb, etc.) and then - using self join - returns the result.
SQL> with test (months, cnt) as
2 (select 'jan', 2 from dual union all
3 select 'feb', 3 from dual union all
4 select 'mar', 5 from dual
5 ),
6 temp as
7 (select
8 months,
9 to_number(to_char(to_date(months, 'mon', 'nls_date_language=english'), 'mm')) mon,
10 cnt
11 from test
12 )
13 select a.months, b.cnt
14 from temp a join temp b on a.mon >= b.mon
15 order by a.mon, b.cnt;
MON CNT
--- ----------
jan 2
feb 2
feb 3
mar 2
mar 3
mar 5
6 rows selected.
SQL>
You need a self join:
select t.months, tt.cnt
from tablename t inner join tablename tt
on extract(month from to_date(t.Months,'MON')) >= extract(month from to_date(tt.Months,'MON'))
order by extract(month from to_date(t.Months,'MON')), tt.cnt
See the demo.
Results:
> MONTHS | CNT
> :----- | --:
> Jan | 2
> Feb | 2
> Feb | 3
> Mar | 2
> Mar | 3
> Mar | 5

How to upload data from previous year if this year's data is unavailable

TABLE : TEST
Batch Year Value
----------------------
A 2014 11
A 2015 0
A 2016 22
A 2017 0
A 2018 13
B 2015 10
B 2016 0
B 2017 29
B 2018 0
C 2013 24
C 2014 0
D 2015 27
D 2016 0
Herein batchwise suppose data is not there in 2015 it should take data from 2014, similarly if data is unavailable in a particular year for a particular batch then data from the previous year should be allocated to that year.
I tried to put case statement by taking only batch A but the problem is that my query is showing subquery return multiple rows.
SELECT BATCH, YEAR,
(CASE WHEN VALUE = 0 THEN
(SELECT A.VALUE FROM TEST A, TEST B WHERE A.YEAR = B.YEAR-1 AND A.VALUE <> '0') ELSE VALUE END)
FROM TEST;
Required Output table:
Test
Batch Year Value
---------------------
A 2014 11
A 2015 11
A 2016 22
A 2017 22
A 2018 13
B 2015 10
B 2016 10
B 2017 29
B 2018 29
C 2013 24
C 2014 24
D 2015 27
D 2016 27
I used below query to achieve your result,
CREATE TABLE TEST1(Batch VARCHAR(10), Year INT, Value INT)
INSERT INTO TEST1
VALUES('A', 2014,11),
('A', 2015,0 ),
('A', 2016,22),
('A', 2017,0 ),
('A', 2018,13),
('B', 2015,10),
('B', 2016,0 ),
('B', 2017,29),
('B', 2018,0 ),
('C', 2013,24),
('C', 2014,0 ),
('D', 2015,27),
('D', 2016,0 )
SELECT batch, year, CASE WHEN value = 0 then LAG(value) OVER(PARTITION BY batch ORDER BY year) ELSE value END AS value
FROM TEST1
ORDER BY batch, year