Calculate Running count in SQL Server excluding certain rows - sql

This is my table/ source data,
|---------------------------------------------------|
|ID | DT | DAY | ATTENDANCE |
|-------+---------------+-----------+---------------|
|89 | 2014-08-23 | NULL | 1 |
|90 | 2014-08-24 | Sunday | NULL |
|91 | 2014-08-25 | NULL | 1 |
|92 | 2014-08-26 | NULL | 1 |
|93 | 2014-08-27 | NULL | 0 |
|94 | 2014-08-28 | NULL | 1 |
|95 | 2014-08-29 | NULL | 0 |
|96 | 2014-08-30 | NULL | 1 |
|97 | 2014-08-31 | Sunday | NULL |
|98 | 2014-08-01 | NULL | 1 |
|99 | 2014-08-02 | NULL | 1 |
|100 | 2014-08-03 | NULL | 0 |
|101 | 2014-08-04 | NULL | 0 |
|102 | 2014-08-05 | NULL | 1 |
|103 | 2014-08-06 | NULL | 1 |
|104 | 2014-08-07 | Sunday | NULL |
|105 | 2014-08-08 | NULL | 1 |
|106 | 2014-08-09 | NULL | 1 |
|107 | 2014-08-10 | NULL | 1 |
|---------------------------------------------------|
I want a result as given. The 5th column [Streak] is what I am trying to calculate. It is a value calculated based on attendance. At any day, If [ATTENDANCE] = 0, [Streak] becomes reset to 0.
|-------------------------------------------------------------|
|ID | DT | DAY | ATTENDANCE | Streak |
|-------+---------------+-----------+---------------+---------|
|89 | 2014-08-23 | NULL | 1 | 1 |
|90 | 2014-08-24 | Sunday | NULL | |
|91 | 2014-08-25 | NULL | 1 | 2 |
|92 | 2014-08-26 | NULL | 1 | 3 |
|93 | 2014-08-27 | NULL | 0 | 0 |
|94 | 2014-08-28 | NULL | 1 | 1 |
|95 | 2014-08-29 | NULL | 0 | 0 |
|96 | 2014-08-30 | NULL | 1 | 1 |
|97 | 2014-08-31 | Sunday | NULL | |
|98 | 2014-08-01 | NULL | 1 | 2 |
|99 | 2014-08-02 | NULL | 1 | 3 |
|100 | 2014-08-03 | NULL | 0 | 0 |
|101 | 2014-08-04 | NULL | 0 | 0 |
|102 | 2014-08-05 | NULL | 1 | 1 |
|103 | 2014-08-06 | NULL | 1 | 2 |
|104 | 2014-08-07 | Sunday | NULL | |
|105 | 2014-08-08 | NULL | 1 | 3 |
|106 | 2014-08-09 | NULL | 1 | 4 |
|107 | 2014-08-10 | NULL | 1 | 5 |
|-------------------------------------------------------------|
This is what I so far did. For me Sunday is also getting incremented as a Day.
Any help, in solving it..
SQL
SELECT X.*, X.ID - LU.FROMID + 1
FROM #TAB X LEFT JOIN
(
SELECT (SELECT MIN(ID) FROM #TAB) FROMID,MIN(ID) TOID FROM #TAB WHERE ATTENDANCE = 0
UNION
SELECT A.ID FROMID,B.ID TOID
FROM (SELECT ID,ROW_NUMBER() OVER (ORDER BY ID) R FROM #TAB WHERE ATTENDANCE = 0) A CROSS JOIN
(SELECT ID,ROW_NUMBER() OVER (ORDER BY ID) R FROM #TAB WHERE ATTENDANCE = 0) B
WHERE A.R = (B.R - 1)
UNION
SELECT MAX(ID),(SELECT MAX(ID) FROM #TAB) FROM #TAB WHERE ATTENDANCE = 0
UNION
SELECT MAX(ID),MAX(ID) + 1 FROM #TAB
) LU
ON X.ID >= LU.FROMID AND X.ID < LU.TOID
Source Data for testing:
SET DATEFORMAT DMY
DECLARE #TAB TABLE (ID INT IDENTITY(89,1),DT DATE,DAY VARCHAR(15),ATTENDANCE BIT)
INSERT INTO #TAB VALUES
('23-08-2014',Null,1),
('24-08-2014','Sunday',Null ),
('25-08-2014',Null ,1),
('26-08-2014',Null ,1),
('27-08-2014',Null ,0),
('28-08-2014',Null ,1),
('29-08-2014',Null ,0),
('30-08-2014',Null ,1),
('31-08-2014','Sunday',Null ),
('01-08-2014',Null ,1),
('02-08-2014',Null ,1),
('03-08-2014',Null ,0),
('04-08-2014',Null ,1),
('05-08-2014',Null ,0),
('06-08-2014',Null ,1),
('07-08-2014','Sunday',Null ),
('08-08-2014',Null ,1),
('09-08-2014',Null ,1),
('10-08-2014',Null ,1)
Thanks in Advance.

#HHH, I added another temp table around #TAB. This works, Please test it and tell.
DECLARE #TAB2 TABLE (MASTERID INT IDENTITY(1,1),ID INT,DT DATE,DAY VARCHAR(15),ATTENDANCE BIT)
INSERT INTO #TAB2
SELECT * FROM #TAB WHERE DAY IS NULL
SELECT Y.*,
LU2.Streak
FROM #TAB Y LEFT JOIN (
SELECT X.ID, X.MASTERID - LU.FROMID + 1 [Streak]
FROM #TAB2 X LEFT JOIN
(
SELECT (SELECT MIN(MASTERID) FROM #TAB2) FROMID,MIN(MASTERID) TOID FROM #TAB2 WHERE ATTENDANCE = 0
UNION
SELECT A.MASTERID FROMID,B.MASTERID TOID
FROM (SELECT MASTERID,ROW_NUMBER() OVER (ORDER BY MASTERID) R FROM #TAB2 WHERE ATTENDANCE = 0) A CROSS JOIN
(SELECT MASTERID,ROW_NUMBER() OVER (ORDER BY MASTERID) R FROM #TAB2 WHERE ATTENDANCE = 0) B
WHERE A.R = (B.R - 1)
UNION
SELECT MAX(MASTERID),(SELECT MAX(MASTERID) FROM #TAB2) FROM #TAB2 WHERE ATTENDANCE = 0
UNION
SELECT MAX(MASTERID),MAX(MASTERID) + 1 FROM #TAB2
) LU
ON X.MASTERID >= LU.FROMID AND X.MASTERID < LU.TOID ) LU2
ON Y.ID = LU2.ID
RESULT:

Like said Gordon, you need to identify groups of consecutive attendace day. But this is another version..
select
t.*,
t2.Seq,
(case ATTENDANCE
when 1 then ROW_NUMBER() over (partition by t2.Seq,t.attendance order by t.id)
when 0 then 0
else null end) Streak
from
#TAB t
outer apply
(select
count(1) as Seq
from
#tab t2
where
t2.id < t.ID
and
t2.ATTENDANCE = 0) as t2
order by
t.id

You need to identify groups of consecutive attendance days. You can do this using the difference of row_numbers() approach. Subtract the row_number() by attendance (and without Sunday) from the overall row_number() (without Sunday). The difference is constant for a streak. The rest is just another row_number() call.
Here is what it looks like:
select t.id, t.dt, t.day, t.attendance,
(case when t.day = 'Sunday' then NULL
else row_number() over (partition by attendance, grp order by dt)
end) as streak
from (select t.*,
(row_number() over (partition by day order by dt) -
row_number() over (partition by day, attendance order by dt)
) as grp
from #tab t
) t;

According to your requirement, i think this query will be useful ,please change the fieldname and table name.it's a very simple way to get calculate simple running total in a table......
SELECT a.id,a.dateday, a.dateval, (SELECT SUM(b.hoursday) FROM #temp b
WHERE b.dateday<=a.dateday) as totalhours
FROM #temp a
Thanks

Related

Querying last non-null values of time-series table in Postgres

I have a time-series table which looks like the following:
time | a | b | c | d
--------------------+---------+----------+---------+---------
2016-05-15 00:08:22 | | | |
2016-05-15 01:50:56 | | | 26.8301 |
2016-05-15 02:41:58 | | | |
2016-05-15 03:01:37 | | | |
2016-05-15 04:45:18 | | | |
2016-05-15 05:45:32 | | | 26.9688 |
2016-05-15 06:01:48 | | | |
2016-05-15 07:47:56 | | | | 27.1269
2016-05-15 08:01:22 | | | |
2016-05-15 09:35:36 | 26.7441 | 29.8398 | | 26.9981
2016-05-15 10:08:53 | | | |
2016-05-15 11:08:30 | | | |
2016-05-15 12:14:59 | | | |
2016-05-15 13:33:36 | 27.4277 | 29.7695 | |
2016-05-15 14:36:36 | 27.4688 | 29.6836 | |
2016-05-15 15:37:36 | 27.1016 | | |
I want to return last non-null values of every column:
like this (best option):
time | column | value
--------------------+--------- +-------
2016-05-15 15:37:36 | a | 27.1016
2016-05-15 14:36:36 | b | 29.6836
2016-05-15 05:45:32 | c | 26.9688
2016-05-15 09:35:36 | d | 26.9981
like this:
column | value
-------- +-------
a | 27.1016
b | 29.6836
c | 26.9688
d | 26.9981
or at least like this:
a | b | c | d
--------+----------+---------+---------
27.1016 | 29.6836 | 26.9688 | 26.9981
Thanks!
You can unpivot and select the last row:
select distinct on (v.which) t.time, v.which, v.val
from t cross join lateral
(values (a, 'a'), (b, 'b'), (c, 'c'), (d, 'd')) v(val, which)
where v.val is not null
order by v.which, t.time desc;
I suggest another answer, but I see now that #GordonLinoff answer is better.
with src as (
select '0' as pos, 1 as a, 2 as b, null as c
union all select '1', null as a, null as b, 7 as c
union all select '2', 2 as a, null as b, 3 as c
union all select '3', null as a, null as b, null as c
union all select '4', null as a, 4 as b, null as c
),
n as (
select row_number() over() as rn, src.* from src
)
(select last_value(pos) over (order by rn desc) as timestamp, 'a' as column, last_value(a) over (order by rn desc) as value
from n
where a is not null
limit 1)
union all
(select last_value(pos) over (order by rn desc) as timestamp, 'b' as column, last_value(b) over (order by rn desc) as value
from n
where b is not null
limit 1)
union all
(select last_value(pos) over (order by rn desc) as timestamp, 'c' as column, last_value(c) over (order by rn desc) as value
from n
where c is not null
limit 1)
timestamp | column | value
:-------- | :----- | ----:
2 | a | 2
4 | b | 4
2 | c | 3
db<>fiddle here

How to move all non-null values to the top of my column?

I have the following data in my table:
| Id | lIST_1 |
----------------------
| 1 | NULL |
| 2 | JASON |
| 3 | NULL |
| 4 | BANDORAN |
| 5 | NULL |
| 6 | NULL |
| 7 | SMITH |
| 8 | NULL |
How can I write a query to get the output below?
| Id | lIST_1
-----------------------
| 1 | JASON |
| 2 | BANDORAN |
| 3 | SMITH |
| 4 | NULL |
| 5 | NULL |
| 6 | NULL |
| 7 | NULL |
| 8 | NULL |
You can use order by:
select row_number() over (order by (select null)) as id, t.list_1
from t
order by (case when list_1 is not null then 1 else 2 end)
It is unclear why you would want id to change values, but you can use row_number() for that.
EDIT:
If you want to change the id, then you can do:
with toupdate as (
select row_number() over (order by (case when list_id is not null then 1 else 2 end), id
) as new_id,
t.*
from t
)
update toupdate
set id = new_id
where id <> new_id; -- no need to update if the value remains the same

SELECT columns outside applied WHERE filter

+----------+---------+--------+------+------------+-----------+
| PersonID | OrderID | PlanID | Plan | Sdate | Edate |
+----------+---------+--------+------+------------+-----------+
| 1 | 1 | 312 | M | 10/14/2016 | 1/30/2017 |
| 1 | 4 | 125 | A | 10/18/2016 | 2/3/2017 |
| 1 | 7 | 411 | B | 10/25/2016 | 4/7/2017 |
| 2 | 1 | 111 | E | 10/31/2016 | 4/21/2017 |
| 2 | 3 | 312 | M | 11/4/2016 | 4/28/2017 |
| 2 | 5 | 253 | L | 11/29/2016 | 5/3/2017 |
| 3 | 1 | 50 | Q | 12/2/2016 | 5/8/2017 |
| 3 | 2 | 12 | W | 12/8/2016 | 6/8/2017 |
| 3 | 4 | 312 | M | 12/22/2016 | 6/26/2017 |
| 3 | 6 | 53 | Z | 12/27/2016 | 7/10/2017 |
+----------+---------+--------+------+------------+-----------+
I need the following output:
+----------+------------+-----------+----------+----------+
| PersonID | SDateM | EDateM | MinPlan1 | MinPlan2 |
+----------+------------+-----------+----------+----------+
| 1 | 10/14/2016 | 1/30/2017 | M | A |
| 2 | 11/4/2016 | 4/28/2017 | E | M |
| 3 | 12/22/2016 | 6/26/2017 | Q | W |
+----------+------------+-----------+----------+----------+
where SDateM is SDate when Plan=M, EDateM is Edate when Plan=M, MinPlan1 is the Plan for the minimum OrderID for a person. And MinPlan2 is the Plan for the 2nd lowest OrderID for a person.
I apply PlanID=312 in the WHERE clause but that restricts my ability to select the MinPlans. I don't know how to write the code to extract MinPlan1 and MinPlan2.
Attempt:
SELECT
PersonID
,Sdate AS SDateM
,Edate AS EDateM
,MinPlan1
,MinPlan2
FROM
T1
WHERE
1 = 1
AND PlanID = 312
CREATE TABLE t_plan
(personID INT,
orderID INT,
planID INT,
plan_n VARCHAR(20),
sdate DATE,
edate DATE
)
INSERT INTO t_plan
VALUES(1,1,312,'M','10/14/2016','1/30/2017'),
(1,4,125,'A','10/18/2016','2/3/2017'),
(1,7,411,'B','10/25/2016','4/7/2017'),
(2,1,111,'E','10/31/2016','4/21/2017'),
(2,3,312,'M','11/4/2016','4/28/2017'),
(2,5,253,'L','11/29/2016','5/3/2017'),
(3,1,50 ,'Q','12/2/2016','5/8/2017'),
(3,2,12 ,'W','12/8/2016','6/8/2017'),
(3,4,312,'M','12/22/2016','6/26/2017'),
(3,6,53 ,'Z','12/27/2016','7/10/2017')
SELECT personid,
MIN(CASE WHEN plan_n = 'M' THEN sdate END) sdateM,
MIN(CASE WHEN plan_n = 'M' THEN edate END) edateM,
MAX(CASE WHEN rnum = 1 THEN plan_n END) minplan1,
MAX(CASE WHEN rnum = 2 THEN plan_n END) minplan2
FROM (SELECT *,
ROW_NUMBER() OVER (PARTITION BY personid ORDER BY orderid) rnum
FROM t_plan
) t
GROUP BY personid;
Result
personid sdateM edateM minplan1 minplan2
1 2016-10-14 2017-01-30 M A
2 2016-11-04 2017-04-28 E M
3 2016-12-22 2017-06-26 Q W
I think a combination of conditional aggregation with row_number() does the trick:
select personid,
min(case when plan = 'M' then sdate end) as sdateM,
min(case when plan = 'M' then edate end) as edateM,
max(case when seqnum = 1 then plan end) as plan_1,
max(case when seqnum = 2 then plan end) as plan_2
from (select t1.*,
row_number() over (partition by personid order by orderid) as seqnum
from t1
) t1
group by person_id;
You can do this using row_number and conditional aggregation.
SELECT PersonID
,MAX(CASE WHEN plan='M' then Sdate END) AS SDateM
,MAX(CASE WHEN plan='M' then Edate END) AS EDateM
,MAX(CASE WHEN rnum=1 then plan END) AS MinPlan1
,MAX(CASE WHEN rnum=2 then plan END) AS MinPlan2
FROM (SELECT t1.*,row_number() over(partition by personId order by orderId) as rnum
FROM T1
) t
GROUP BY PersonID

Count consecutive NULL values for given ID

I'm trying to create a query that contains a running count of ISO weeks when the account has no entry in a [Volume] table. The query is to return only a sample of the accounts, so I've created a couple of CTEs to limit the number of records and join the volume table so that weeks with no volume show up in the results.
To illustrate the result I'm looking for:
ISOWk | SurrID | Weekly Volume | No vol Count
201601 | 001 | 0 | 1
201601 | 002 | 5 | 0
201602 | 001 | 0 | 2
201602 | 002 | 0 | 1
201603 | 001 | 125| 0
201603 | 002 | 75 | 0
201604 | 001 | 0 | 1
201604 | 002 | 75 | 0
As you can see, account with SurrID 001 has no volume for weeks 201601 and 201602 so has [No vol Count] of 2 in week 201602. In week 201603, there is volume so the counter resets to 0 and increases to 1 week 201604.
From the research I've done I've managed to get a consecutive count running using ROW_NUMBER with a window function, but it doesn't reset if there is volume (as in week 201603 in my example). What I can't figure out is how to count consecutive zero values and reset when needed.
I've included my full query below so that you get the full picture (please point out any particularly bad practices here - I'm still finding my way!). It all works as intended until I include the third CTE "NDs". It then takes 45 mins to return just over 2000 rows and returns an non-resetting count for rows showing no weekly volume.
WITH Surrs AS
(
SELECT SurrID, OracleStartDate AS OSD
FROM (
SELECT ca.SurrID, OracleStartDate, ROW_NUMBER() OVER(ORDER BY OracleStartDate) as rn
FROM tblCustomerAccounts ca
JOIN tblAccountUpdates au
ON ca.SurrID = au.SurrID
WHERE CustomerType_ID IN (1,2,3,4,5,6,7,12)
AND au.ISOWk = 201641
) a
WHERE rn % 1000 = 0
),
Updates AS
(
SELECT au.ISOWk, s.SurrID, (CASE WHEN AccStatus_ID = 1 THEN 'A' ELSE 'I' END) AS AccStatus, (CASE WHEN dbo.udf_ConvertDateToISOWeek(OSD) <= BBC THEN 'B' ELSE 'F' END) AS Book
FROM Surrs s
JOIN (
SELECT ISOWk,
(SELECT BBCutOff FROM dbo.udf_CutOffWeeks(ISOWk)) AS BBC,
(SELECT FYStart FROM dbo.udf_CutOffWeeks(ISOWk)) AS FYS,
(SELECT FYEnd FROM dbo.udf_CutOffWeeks(ISOWk)) AS FYE,
(SELECT BYStart FROM dbo.udf_CutOffWeeks(ISOWk)) AS BYS,
(SELECT BYEnd FROM dbo.udf_CutOffWeeks(ISOWk)) AS BYE,
SurrID,
AccStatus_ID
FROM tblAccountUpdates
) au
ON au.SurrID = s.SurrID
),
NDs AS
(
SELECT u.ISOWk, u.SurrID, ROW_NUMBER() OVER (PARTITION BY u.SurrID ORDER BY u.ISOWk) AS NDCount
FROM Updates u
LEFT JOIN tblTotalVolumes tv
ON u.SurrID = tv.SurrID
AND u.ISOWk = tv.ISOWk
WHERE tv.Volume IS NULL
AND u.ISOWk >= 201601
)
SELECT tw.ISOWk,
tw.SurrID,
(CASE WHEN Volume IS NULL THEN 0 ELSE Volume END) AS [Weekly Volume],
tw.Book,
tw.AccStatus,
(CASE WHEN tw.AccStatus = 'I' AND lw.AccStatus = 'A' THEN 'Y' ELSE '' END) AS [Stopped this week],
(CASE WHEN tw.AccStatus = 'A' AND lw.AccStatus = 'I' THEN 'Y' ELSE '' END) AS [Restarted this week],
(CASE WHEN NDCount IS NULL THEN 0 ELSE NDCount END) AS [Consecutive ND Weeks]
FROM Updates tw
JOIN Updates lw
ON lw.ISOWk = dbo.udf_ConvertDateToISOWeek(DATEADD("ww",-1,dbo.udf_ConvertISOWkToDate(tw.ISOWk)))
AND tw.SurrID = lw.SurrID
LEFT JOIN tblTotalVolumes tv
ON tw.SurrID = tv.SurrID
AND tw.ISOWk = tv.ISOWk
LEFT JOIN NDs
ON tw.SurrID = nds.SurrID
AND tw.ISOWk = nds.ISOWk
ORDER BY tw.ISOWk
To reiterate what I need: the column [Consecutive ND Weeks] should count consecutive weeks where [Weekly Volume] is 0. Help will be much appreciated.
Thanks
UPDATE:
I've tried to implement #Gordon Linoff's post but my counter does not reset to 0 when there is a value for [Weekly Volume]. Here's my amended query:
SELECT t.*, (CASE WHEN [Weekly Volume] = 0 THEN ROW_NUMBER() OVER (PARTITION BY t.SurrID, grp ORDER BY ISOWk) ELSE 0 END) AS [ND Count]
FROM (
SELECT tw.ISOWk,
s.SurrID,
tw.AccStatus,
(CASE WHEN tv.Volume IS NULL THEN 0 ELSE tv.Volume END) AS [Weekly Volume],
(CASE WHEN dbo.udf_ConvertDateToISOWeek(OSD) <= BBC THEN 'B' ELSE 'F' END) AS Book,
(CASE WHEN tw.AccStatus = 'I' AND lw.AccStatus = 'A' THEN 'Y' ELSE '' END) AS [Stopped this week],
(CASE WHEN tw.AccStatus = 'A' AND lw.AccStatus = 'I' THEN 'Y' ELSE '' END) AS [Restarted this week],
SUM(CASE WHEN tv.volume > 0 THEN 1 ELSE 0 END) OVER(PARTITION BY tv.SurrID ORDER BY tv.ISOWk) AS grp
FROM (
SELECT SurrID, OracleStartDate AS OSD
FROM (
SELECT ca.SurrID, OracleStartDate, ROW_NUMBER() OVER(ORDER BY OracleStartDate) as rn
FROM tblCustomerAccounts ca
JOIN tblAccountUpdates au
ON ca.SurrID = au.SurrID
WHERE CustomerType_ID IN (1,2,3,4,5,6,7,12)
AND au.ISOWk = 201641
) a
WHERE rn % 1000 = 0
) s
JOIN (
SELECT ISOWk,
(SELECT BBCutOff FROM dbo.udf_CutOffWeeks(ISOWk)) AS BBC,
(SELECT FYStart FROM dbo.udf_CutOffWeeks(ISOWk)) AS FYS,
(SELECT FYEnd FROM dbo.udf_CutOffWeeks(ISOWk)) AS FYE,
(SELECT BYStart FROM dbo.udf_CutOffWeeks(ISOWk)) AS BYS,
(SELECT BYEnd FROM dbo.udf_CutOffWeeks(ISOWk)) AS BYE,
SurrID,
(CASE WHEN AccStatus_ID = 1 THEN 'A' ELSE 'I' END) AS AccStatus
FROM tblAccountUpdates
) tw
ON tw.SurrID = s.SurrID
JOIN (
SELECT ISOWk,
SurrID,
(CASE WHEN AccStatus_ID = 1 THEN 'A' ELSE 'I' END) AS AccStatus
FROM tblAccountUpdates
) lw
ON tw.SurrID = lw.SurrID
AND dbo.udf_ConvertDateToISOWeek(DATEADD("ww",-1,dbo.udf_ConvertISOWkToDate(tw.ISOWk))) = lw.ISOWk
LEFT JOIN tblTotalVolumes tv
ON tw.ISOWk = tv.ISOWk
AND tw.SurrID = tv.SurrID
) t
ORDER BY ISOWk
UPDATE:
I've now modified my query to reflect Vladimir's solution (once again, this is the full query):
SELECT ISOWk,
SurrID,
AccStatus,
[Weekly Volume],
Book,
[Stopped this week],
[Restarted this week],
RN1,
RN2,
grp,
rn3,
(CASE WHEN [Weekly Volume] = 0 THEN rn3 ELSE 0 END) AS [ND Count]
FROM (
SELECT t.ISOWk,
t.SurrID,
t.AccStatus,
t.[Weekly Volume],
t.Book,
t.[Stopped this week],
t.[Restarted this week],
rn1,
rn2,
rn1 - rn2 AS grp,
ROW_NUMBER() OVER(PARTITION BY t.SurrID, rn1-rn2 ORDER BY ISOWk) AS rn3
FROM (
SELECT tw.ISOWk,
s.SurrID,
tw.AccStatus,
(CASE WHEN tv.Volume IS NULL THEN 0 ELSE tv.Volume END) AS [Weekly Volume],
(CASE WHEN dbo.udf_ConvertDateToISOWeek(OSD) <= BBC THEN 'B' ELSE 'F' END) AS Book,
(CASE WHEN tw.AccStatus = 'I' AND lw.AccStatus = 'A' THEN 'Y' ELSE '' END) AS [Stopped this week],
(CASE WHEN tw.AccStatus = 'A' AND lw.AccStatus = 'I' THEN 'Y' ELSE '' END) AS [Restarted this week],
ROW_NUMBER() OVER(PARTITION BY tw.SurrID ORDER BY tw.ISOWk) AS rn1,
ROW_NUMBER() OVER(PARTITION BY tw.SurrID, tv.Volume ORDER BY tw.ISOWk) AS rn2
FROM (
SELECT SurrID, OracleStartDate AS OSD
FROM (
SELECT ca.SurrID, OracleStartDate, ROW_NUMBER() OVER(ORDER BY OracleStartDate) as rn
FROM tblCustomerAccounts ca
JOIN tblAccountUpdates au
ON ca.SurrID = au.SurrID
WHERE CustomerType_ID IN (1,2,3,4,5,6,7,12)
AND au.ISOWk = 201641
) a
WHERE rn % 2000 = 0
) s
JOIN (
SELECT ISOWk,
(SELECT BBCutOff FROM dbo.udf_CutOffWeeks(ISOWk)) AS BBC,
(SELECT FYStart FROM dbo.udf_CutOffWeeks(ISOWk)) AS FYS,
(SELECT FYEnd FROM dbo.udf_CutOffWeeks(ISOWk)) AS FYE,
(SELECT BYStart FROM dbo.udf_CutOffWeeks(ISOWk)) AS BYS,
(SELECT BYEnd FROM dbo.udf_CutOffWeeks(ISOWk)) AS BYE,
SurrID,
(CASE WHEN AccStatus_ID = 1 THEN 'A' ELSE 'I' END) AS AccStatus
FROM tblAccountUpdates
) tw
ON tw.SurrID = s.SurrID
JOIN (
SELECT ISOWk,
SurrID,
(CASE WHEN AccStatus_ID = 1 THEN 'A' ELSE 'I' END) AS AccStatus
FROM tblAccountUpdates
) lw
ON tw.SurrID = lw.SurrID
AND dbo.udf_ConvertDateToISOWeek(DATEADD("ww",-1,dbo.udf_ConvertISOWkToDate(tw.ISOWk))) = lw.ISOWk
LEFT JOIN tblTotalVolumes tv
ON tw.ISOWk = tv.ISOWk
AND tw.SurrID = tv.SurrID
) t
) x
ORDER BY ISOWk
Here's a sample of the unexpected (in that the counter doesn't reset to 0 when [Weekly Volume] has a value greater than 0, identified with the asterisks) results. The following all have the same ID, so I've removed the ID column.
ISOWk | Weekly Volume | rn1 | rn2 | grp | rn3 | ND Count |
201620 | 0 | 1 | 1 | 0 | 1 | 1 |
201621 | 0 | 2 | 2 | 0 | 2 | 2 |
201622 | 0 | 3 | 3 | 0 | 3 | 3 |
201623 | 0 | 4 | 4 | 0 | 4 | 4 |
201624 | 0 | 5 | 5 | 0 | 5 | 5 |
201625 | 53 | 6 | 1 | 5 | 1 | 0 |
201626 | 49 | 7 | 1 | 6 | 1 | 0 |
201627 | 98 | 8 | 1 | 7 | 1 | 0 |
201628 | 54 | 9 | 1 | 8 | 1 | 0 |
201629 | 53 | 10 | 2 | 8 | 2 | 0 |
201630 | 103 | 11 | 1 | 10 | 1 | 0 |
201631 | 59 | 12 | 1 | 11 | 1 | 0 |
201632 | 35 | 13 | 1 | 12 | 1 | 0 |
201633 | 0 | 14 | 6 | 8 | 3 | 3 |**
201634 | 0 | 15 | 7 | 8 | 4 | 4 |**
201635 | 0 | 16 | 8 | 8 | 5 | 5 |**
201636 | 0 | 17 | 9 | 8 | 6 | 6 |**
201637 | 87 | 18 | 1 | 17 | 1 | 0 |
201638 | 136 | 19 | 1 | 18 | 1 | 0 |
201639 | 56 | 20 | 1 | 19 | 1 | 0 |
201640 | 70 | 21 | 1 | 20 | 0 | 0 |
201641 | 77 | 22 | 1 | 21 | 1 | 0 |
There are other instances of this issue in my dataset.
It looks like a gaps-and-islands problem.
Sample data
DECLARE #T TABLE(ISOWk int, SurrID char(3), WeeklyVolume int);
INSERT INTO #T(ISOWk, SurrID, WeeklyVolume) VALUES
(201601, '001', 0),
(201601, '002', 5),
(201602, '001', 0),
(201602, '002', 0),
(201603, '001',125),
(201603, '002', 75),
(201604, '001', 0),
(201604, '002', 75),
(201620, '003', 0),
(201621, '003', 0),
(201622, '003', 0),
(201623, '003', 0),
(201624, '003', 0),
(201625, '003', 53),
(201626, '003', 49),
(201627, '003', 98),
(201628, '003', 54),
(201629, '003', 53),
(201630, '003',103),
(201631, '003', 59),
(201632, '003', 35),
(201633, '003', 0),
(201634, '003', 0),
(201635, '003', 0),
(201636, '003', 0),
(201637, '003', 87),
(201638, '003',136),
(201639, '003', 56),
(201640, '003', 70),
(201641, '003', 77),
(201601, '004', 0),
(201602, '004', 6),
(201603, '004', 0),
(201604, '004', 0);
I added your extended sample with SurrID=003 and added mine sample with SurrID=004.
Query
WITH
CTE
AS
(
SELECT
ISOWk
,SurrID
,WeeklyVolume
,ROW_NUMBER() OVER (PARTITION BY SurrID ORDER BY ISOWk) AS rn1
,ROW_NUMBER() OVER (PARTITION BY SurrID,WeeklyVolume ORDER BY ISOWk) AS rn2
FROM #T
)
,CTE2
AS
(
SELECT
ISOWk
,SurrID
,WeeklyVolume
,rn1
,rn2
,rn1-rn2 AS grp
,ROW_NUMBER() OVER (PARTITION BY SurrID,WeeklyVolume,rn1-rn2 ORDER BY ISOWk) AS rn3
FROM CTE
)
SELECT
ISOWk
,SurrID
,WeeklyVolume
,rn1
,rn2
,grp
,rn3
,CASE WHEN WeeklyVolume = 0 THEN rn3 ELSE 0 END AS NoVolumeCount
FROM CTE2
ORDER BY SurrID, ISOWk;
Result
+--------+--------+--------------+-----+-----+-----+-----+---------------+
| ISOWk | SurrID | WeeklyVolume | rn1 | rn2 | grp | rn3 | NoVolumeCount |
+--------+--------+--------------+-----+-----+-----+-----+---------------+
| 201601 | 001 | 0 | 1 | 1 | 0 | 1 | 1 |
| 201602 | 001 | 0 | 2 | 2 | 0 | 2 | 2 |
| 201603 | 001 | 125 | 3 | 1 | 2 | 1 | 0 |
| 201604 | 001 | 0 | 4 | 3 | 1 | 1 | 1 |
| 201601 | 002 | 5 | 1 | 1 | 0 | 1 | 0 |
| 201602 | 002 | 0 | 2 | 1 | 1 | 1 | 1 |
| 201603 | 002 | 75 | 3 | 1 | 2 | 1 | 0 |
| 201604 | 002 | 75 | 4 | 2 | 2 | 2 | 0 |
| 201620 | 003 | 0 | 1 | 1 | 0 | 1 | 1 |
| 201621 | 003 | 0 | 2 | 2 | 0 | 2 | 2 |
| 201622 | 003 | 0 | 3 | 3 | 0 | 3 | 3 |
| 201623 | 003 | 0 | 4 | 4 | 0 | 4 | 4 |
| 201624 | 003 | 0 | 5 | 5 | 0 | 5 | 5 |
| 201625 | 003 | 53 | 6 | 1 | 5 | 1 | 0 |
| 201626 | 003 | 49 | 7 | 1 | 6 | 1 | 0 |
| 201627 | 003 | 98 | 8 | 1 | 7 | 1 | 0 |
| 201628 | 003 | 54 | 9 | 1 | 8 | 1 | 0 |
| 201629 | 003 | 53 | 10 | 2 | 8 | 1 | 0 |
| 201630 | 003 | 103 | 11 | 1 | 10 | 1 | 0 |
| 201631 | 003 | 59 | 12 | 1 | 11 | 1 | 0 |
| 201632 | 003 | 35 | 13 | 1 | 12 | 1 | 0 |
| 201633 | 003 | 0 | 14 | 6 | 8 | 1 | 1 |
| 201634 | 003 | 0 | 15 | 7 | 8 | 2 | 2 |
| 201635 | 003 | 0 | 16 | 8 | 8 | 3 | 3 |
| 201636 | 003 | 0 | 17 | 9 | 8 | 4 | 4 |
| 201637 | 003 | 87 | 18 | 1 | 17 | 1 | 0 |
| 201638 | 003 | 136 | 19 | 1 | 18 | 1 | 0 |
| 201639 | 003 | 56 | 20 | 1 | 19 | 1 | 0 |
| 201640 | 003 | 70 | 21 | 1 | 20 | 1 | 0 |
| 201641 | 003 | 77 | 22 | 1 | 21 | 1 | 0 |
| 201601 | 004 | 0 | 1 | 1 | 0 | 1 | 1 |
| 201602 | 004 | 6 | 2 | 1 | 1 | 1 | 0 |
| 201603 | 004 | 0 | 3 | 2 | 1 | 1 | 1 |
| 201604 | 004 | 0 | 4 | 3 | 1 | 2 | 2 |
+--------+--------+--------------+-----+-----+-----+-----+---------------+
I included intermediate columns in the result, so you can understand how it works.
Standard approach for gaps-and-islands is two sequences of ROW_NUMBER - one is a plain sequence (rn1), second is partitioned by WeeklyVolume (rn2).
The difference between rn1 and rn2 gives the ID of the group (island) (grp). Calculate another sequence of row numbers partitioned by WeeklyVolume and this group (rn3) and use it only when WeeklyVolume is zero.
Obviously, everything above is partitioned by SurrID at first.
In the first variant of the answer I forgot to include WeeklyVolume in the last partitioning for rn3.
Here is an approach:
Count the number of preceding non-0 values for SurrId. This number identifies groups.
Do a row_number() within each group.
Only consider the row_number() when the value is 0.
This results in:
select t.*,
(case when weeklyvolume = 0
then row_number() over (partition by SurrId, grp order by ISOwk)
else 0
end) as NoVolCount
from (select t.*,
sum(case when weeklyvolume > 0 then 1 else 0 end) over (partition by SurrId order by ISOwk) as grp
from t
) t;

fetch most recent non null record

I'm trying to fetch the most recent record and find a non-NULL match. The problem is my subquery returns more than one result.
Data set
| ID | DD | SIG_ID | DCRP |
----------------------------------------
| 1 | 2010-06-01 | 1 | Expert |
| 2 | 2010-09-01 | 1 | Expert |
| 3 | 2010-12-01 | 1 | Expert |
| 4 | 2010-12-01 | 1 | Expert II |
| 5 | 2011-03-01 | 1 | Expert II |
| 6 | 2011-06-01 | 1 | (null) |
| 7 | 2010-06-01 | 2 | Senior |
| 8 | 2010-09-01 | 2 | Senior |
| 9 | 2010-09-01 | 2 | Senior |
| 10 | 2010-12-01 | 2 | Senior II |
| 11 | 2011-03-01 | 2 | (null) |
| 12 | 2011-03-01 | 2 | Senior |
| 13 | 2010-06-01 | 3 | (null) |
| 14 | 2010-09-01 | 3 | (null) |
| 15 | 2010-12-01 | 3 | (null) |
Query
SELECT a.sig_id, a.id,
CASE
WHEN b.dcrp IS NULL
THEN
(SELECT dcrp
FROM tbl
WHERE sig_id = a.sig_id
AND id < a.id
AND dcrp IS NOT NULL)
ELSE b.dcrp
END AS dcrp
FROM
(SELECT sig_id, MAX(id) id
FROM tbl
GROUP BY sig_id) a
LEFT JOIN
(SELECT id, dcrp
FROM tbl
WHERE dcrp IS NOT NULL) b ON b.id = a.id
Desired result
Fetch the most recent dcrp for each sig_id:
| ID | DD | SIG_ID | DCRP |
----------------------------------------
| 5 | 2011-03-01 | 1 | Expert II |
| 12 | 2011-03-01 | 2 | Senior |
| 15 | 2010-12-01 | 3 | (null) |
SQL Fiddle
You can use the following:
;WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY SIG_ID
ORDER BY CASE WHEN DCRP IS NOT NULL THEN 0 ELSE 1 END,
DD DESC) RN
FROM tbl
)
SELECT *
FROM CTE
WHERE RN = 1
And the fiddle.
;with si as (
select distinct sig_id from tbl
)
select *
from si
cross apply (select top 1 * from tbl where si.sig_id=tbl.sig_id order by case when dcrp is null then 1 else 0 end asc,dd desc) sii
and with fiddler :
http://sqlfiddle.com/#!3/8e267/2/0
The query in SQLFiddle fails due to subquery returning more than 1 row.
Adding TOP 1 fixes that. Please check if it is OK.
THEN
(SELECT TOP 1 dcrp
FROM tbl
WHERE sig_id = a.sig_id
AND id < a.id
AND dcrp IS NOT NULL)