Repeating value of previous row in a join - tsql

I have one table including accounts and their balance. I would like to report the balance for each day while for missing days report the last day.
Table accounts:
AccountName Date Balance
thomas 2008-10-09 1000
thomas 2008-10-20 5000
david 2008-02-18 2000
david 2008-03-10 200000
let's say we want the report for 2018-10 I need to get something like this
thomas 2008-10-01 0
...
thomas 2008-10-09 1000
thomas 2008-10-10 1000
...
thomas 2008-10-20 5000
...
thomas 2008-10-31 5000
I went this far:
DECLARE #StartDate datetime = '2008/10/9';
DECLARE #EndDate datetime = '2008/10/20';
WITH theDates AS
(
SELECT #StartDate as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM theDates
WHERE DATEADD(day, 1, theDate) <= #EndDate
)
select * from accounts a
right outer join thedates d on a.date=d.theDate
order by thedate
Results:
AccountNo Date Balance theDate
----------- ---------- -------- ----------
thomas 2008-10-09 1000 2008-10-09
NULL NULL NULL 2008-10-10
NULL NULL NULL 2008-10-11
NULL NULL NULL 2008-10-12
NULL NULL NULL 2008-10-13
NULL NULL NULL 2008-10-14
NULL NULL NULL 2008-10-15
NULL NULL NULL 2008-10-16
NULL NULL NULL 2008-10-17
NULL NULL NULL 2008-10-18
NULL NULL NULL 2008-10-19
thomas 2008-10-20 5000 2008-10-20
Any idea?
Update:
I end up using cursor. This is version working perfectly including the situation where an account has no entry.
DECLARE #Date datetime
declare #result table (accountname nvarchar(50), balance int, date datetime)
DECLARE #StartDate datetime = '2008/10/1';
DECLARE #EndDate datetime = '2008/10/29';
declare cur cursor for
WITH theDates AS
(
SELECT #StartDate as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM theDates
WHERE DATEADD(day, 1, theDate) <= #EndDate
)
select * from theDates
open cur
fetch next from cur into #date
while ##FETCH_STATUS=0
begin
insert into #result
select b.accountName, isnull(balance,
(select isnull((select top 1 balance from accounts where date<#date and accountName=b.accountName order by date desc),0))
), #date from
(select * from accounts where date = #date) a
right outer join (select distinct(accountname) from accounts ) b on a.accountname = b.accountname
fetch next from cur into #date
end
close cur
deallocate cur
select * from #result

Try this:
DECLARE #StartDate datetime = '2008/10/9';
DECLARE #EndDate datetime = '2008/10/20';
WITH theDates AS
(
SELECT #StartDate as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM theDates
WHERE DATEADD(day, 1, theDate) <= #EndDate
),
acc AS(
SELECT a.AccountName,
a.Balance,
a.Date,
isnull(c.CloseDate, cast(GETDATE()as date)) as CloseDate
FROM accounts a
CROSS APPLY(SELECT MIN(b.Date) as CloseDate
FROM accounts b
WHERE b.Date > a.Date) c
)
SELECT a.AccountName, a.Balance, a.Date, d.theDate
FROM acc a, theDates d
WHERE a.Date <= d.theDate
AND a.CloseDate > d.theDate
option (maxrecursion 0)
Results:
AccountName Balance Date theDate
----------- ----------- ------------------- -----------------------
thomas 1000 2008-10-09 00:00:00 2008-10-09 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-10 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-11 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-12 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-13 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-14 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-15 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-16 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-17 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-18 00:00:00.000
thomas 1000 2008-10-09 00:00:00 2008-10-19 00:00:00.000
thomas 5000 2008-10-20 00:00:00 2008-10-20 00:00:00.000

You can try to use aggregate function MIN and MAX make calendar table then OUTER JOIN
WITH theDates AS
(
SELECT AccountName, MIN(Date) as StartDt,MAX(Date) EndDt
FROM accounts
GROUP BY AccountName
UNION ALL
SELECT AccountName,DATEADD(day, 1, StartDt),EndDt
FROM theDates
WHERE DATEADD(day, 1, StartDt) <= EndDt
)
select d.AccountName,
d.StartDt [date],
ISNULL(a.Balance,0) Balance
from accounts a
LEFT join thedates d on a.date=d.StartDt
order by StartDt

Related

Show every week of the Year even if there is no data

I have query that pulls data by week and groups it together. But i does not display weeks that doesn't have any data. I want show all weeks even if they don't have data as null maybe
Here is the query if someone can help me with this it will awesome
SELECT
DATEADD (week, datediff(week, 0, StartDate), -1) as 'WeekOf'
,DATEADD (week, datediff(week, 0, StartDate), +5) as 'to'
,DATEPART(wk, StartDate) as 'WeekNumber'
FROM [DESOutage].[dbo].[OPSInterruption]
Where StartDate > '2020-01-01' and EndDate <'2020-02-01'
Group by DATEADD (week, datediff(week, 0, StartDate), -1),DATEPART(wk, StartDate),DATEADD (week, datediff(week, 0, StartDate), +5)
***************Output***************
As you could see week 2 and 4 is missing out since there is no data being returned. I would still like to see week 2 and 4 in the output with maybe 0 as result.
WeekOf to WeekNumber
2019-12-29 00:00:00.000 2020-01-04 00:00:00.000 1
2020-01-12 00:00:00.000 2020-01-18 00:00:00.000 3
2020-01-26 00:00:00.000 2020-02-01 00:00:00.000 5
You probably need a calendar table. Here is a quick way of generating one, with an untested implementation of your code. I am assuming that the StartDate may contain a time component thus the need to coalesce the dates.
DECLARE #StartYear DATETIME = '20200101'
DECLARE #days INT = 366
;WITH
E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 1*10^1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), -- 1*10^2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), -- 1*10^4 or 10,000 rows
E8(N) AS (SELECT 1 FROM E4 a, E4 b), -- 1*10^8 or 100,000,000 rows
Tally(N) AS (SELECT TOP (#Days) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E8),
Calendar AS (
SELECT StartOfDay = DATEADD(dd,N-1,#StartYear),
EndOfDay = DATEADD(second, -1, DATEADD(dd,N ,#StartYear))
FROM Tally)
SELECT DATEADD (week, datediff(week, 0, COALESCE(x.StartDate, c.StartOfDay) ), -1) as 'WeekOf'
, DATEADD (week, datediff(week, 0, COALESCE(x.StartDate, c.StartOfDay)), +5) as 'to'
, DATEPART(wk, COALESCE(x.StartDate, c.StartOfDay)) as 'WeekNumber'
FROM Calendar c
INNER JOIN [DESOutage].[dbo].[OPSInterruption] x
ON x.StartDate > c.StartOfDay AND x.StartDate <= c.EndOfDay
WHERE c.StartOfDay > '2020-01-01' AND c.StartOfDay <'2020-02-01'
GROUP BY DATEADD (week, datediff(week, 0, COALESCE(x.StartDate, c.StartOfDay)), -1),
DATEPART(wk, COALESCE(x.StartDate, c.StartOfDay)),
DATEADD (week, datediff(week, 0, COALESCE(x.StartDate, c.StartOfDay)), +5)

DateDiff Rows Where UserID is a match

On Sql Server 2012 (T-SQL), I would like to analyse the date difference between the end dates and start dates for the same userid, and to see if there is a equal or greater than twelve month gap between times.
So for which ContractID the start date is =>12m than the previous end date.
ContractID UserID StartDate EndDate 12m Lapse
1 779 01/01/2000 01/01/2010 False
2 779 01/01/2010 01/01/2015 False
3 779 01/01/2016 NULL True
4 1021 09/03/2008 NULL False
Things perhaps to note are the userID is not in order on the real table, only the contractID is.
Using a CTE and the LAG() window function it's quite easy:
Create sample data:
DECLARE #T as table
(
ContractID int,
UserID int,
StartDate date,
EndDate date
)
INSERT INTO #T VALUES
(1, 779, '01/01/2000', '01/01/2010'),
(2, 779, '01/01/2010', '01/01/2015'),
(3, 779, '01/01/2016', NULL),
(4, 1021, '09/03/2008', NULL)
The query:
;WITH CTE AS
(
SELECT ContractID,
UserID,
StartDate,
EndDate,
LAG(EndDate) OVER(PARTITION BY UserId ORDER BY StartDate) As PreviousEndDate
FROM #T
)
SELECT ContractID,
UserID,
StartDate,
EndDate,
CASE WHEN DATEDIFF(MONTH, ISNULL(PreviousEndDate, StartDate), StartDate) >= 12 THEN
'True'
ELSE
'False'
END As '12m Lapse'
FROM CTE
Results:
ContractID UserID StartDate EndDate 12m Lapse
----------- ----------- ---------- ---------- ---------
1 779 2000-01-01 2010-01-01 False
2 779 2010-01-01 2015-01-01 False
3 779 2016-01-01 NULL True
4 1021 2008-09-03 NULL False
SELECT * FROM Table WHERE DATEDIFF(M,StartDate,EndDate) >=12
Starting with SQL Server 2012, there is a function called Lag that will help you get what you need.
The partition by of the window function will make sure that its separated by userID, and the order by will make sure its in ContractID order.
with prevEndDate as
(
select t.contractID
, t.userID
, t.startDate
, t.endDate
, lag(t.endDate,1,NULL) over (partition by t.userID order by t.contractID asc) as prevEndDate
from db_name.dbo.myTable as t
)
select p.contractID
, p.userID
, p.startDate
, p.endDate
, case when datediff(m,p.prevEndDate, p.startDate) >= 12 then 'True' else 'False' end as [12m Lapse]
from prevEndDate as p

Forming a Tsql query that ranks and categorizes date field

I have this datset:
create table #date_example
(
date_val datetime, rownum int
)
insert #date_example values('3/1/14',1)
insert #date_example values('3/1/14',2)
insert #date_example values('3/1/14',3)
insert #date_example values('2/1/14',4)
insert #date_example values('1/3/14',5)
select --top 1 with ties
date_val,
ROW_NUMBER() OVER(PARTITION BY rownum ORDER BY date_val DESC) AS 'RowNum'
from #date_example
order by date_val
desc
With output:
date_val RowNum
2014-03-01 00:00:00.000 1
2014-03-01 00:00:00.000 1
2014-03-01 00:00:00.000 1
2014-02-01 00:00:00.000 1
2014-01-03 00:00:00.000 1
But I want instead output:
date_val RowNum
2014-03-01 00:00:00.000 1
2014-03-01 00:00:00.000 1
2014-03-01 00:00:00.000 1
2014-02-01 00:00:00.000 2
2014-01-03 00:00:00.000 3
So I want the RowNum to be a ranking which includes ties. How can I do this?
I found the answer from another post:
select
date_val,
Rank() OVER(ORDER BY date_val DESC) AS 'RowNum'
from #date_example

Comparison of two rows in TSQL

Good Day
I have the following result set returned:
financeYearEnd FromDate ToDate ClientPortfolioCode
2013-12-31 00:00:00.000 2014-01-01 2014-01-31 C1
2013-12-31 00:00:00.000 2014-01-01 2014-01-31 C2
2012-12-31 00:00:00.000 2013-12-01 2013-12-31 C1
2012-12-31 00:00:00.000 2013-12-01 2013-12-31 C2
What I need to do is the following:
I need to compare the financeYearEnd of all the C1 Fields (there will always only be two), and see if they are different to each other
2013-12-31 00:00:00.000 2014-01-01 2014-01-31 C1
2012-12-31 00:00:00.000 2013-12-01 2013-12-31 C1
As seen, the financeYearEnd does differ, so I need to store that result as a row in a temporary Table.
This needs to be done for all distinct ClientPortfolioCodes(Which will always appear in groups of two)
How can this be achieved?
I have tried select distinct .. - didn't work. It returned all my rows
EDIT -
WITH cteCompareTopTwoYears
AS (
SELECT TOP (
SELECT COUNT(*) * 2
FROM #ClientPortFolios
) FinancialYearEnd AS financeYearEnd
,FromDate
,ToDate
,CA.ClientPortfolioCode
FROM rpt.F3_fn_ClientPortfolios_CapitalAccount_IncludingYTD CA
WHERE (
(
CA.FromDate = (DATEADD(m, - 1, #FromDate))
AND CA.ToDate = (DATEADD(m, - 1, #ToDate))
)
OR (
CA.FromDate = #FromDate
AND CA.ToDate = #ToDate
)
)
AND (
CA.ClientPortFolioCode IN (
SELECT ClientPortfolioCode
FROM #ClientPortfolios
)
)
ORDER BY FromDate DESC
)
SELECT *
FROM cteCompareTopTwoYears c
Perhaps with the help of ROW_NUMBER, e.g.:
WITH CTE AS
(
SELECT financeYearEnd, FromDate, ToDate, ClientPortfolioCode,
rn = row_Number () OVER (Partition By ClientPortfolioCode
Order By financeYearEnd ASC)
FROM dbo.TableName
)
SELECT financeYearEnd, FromDate, ToDate, ClientPortfolioCode
FROM CTE
WHERE rn > 1
Demo

Understanding TSQL Coalesce function

I am trying to select all 12 months / year. And I thought following TSQL code would do this. However, this does not include all months like I want. What is the cause of this? This is modified code:
DECLARE #END_YEAR VARCHAR(10)
DECLARE #END_MONTH VARCHAR(10)
SET #END_YEAR = '2010'
SET #END_MONTH = '10'
DECLARE #TheMonthLastDate DATETIME
DECLARE #TempDate DATETIME
SET #TempDate = '2010-11-01 00:00:00.000'
SET #TheMonthLastDate = '2010-11-01 00:00:00.000'
;with months
as
(
select dateadd(month, -1, dateadd(day, datediff(day, 0, #TempDate), 0)) as m
union all
select dateadd(month, -1, m)
from months
where m > dateadd(month, -12, #TempDate)
)
,yourTable(DateOpened, DateClosed)
as
(select TSK_START_DATE, BTK_CLOSED_DATE
FROM [PROC].ALL_AUDIT
WHERE
(BTK_CLOSED_DATE < #TheMonthLastDate OR
TSK_START_DATE < #TheMonthLastDate
)
)
select yt.DateClosed 'r2', m.m 'r3',
month(coalesce(yt.DateClosed, m.m)) as 'MonthClosed',
year(coalesce(yt.DateClosed, m.m)) as 'YearClosed'
from months m
left join yourTable yt
on
( datepart(year, yt.DateClosed) = DATEPART(year, m.m)
and datepart(month, yt.DateClosed) = DATEPART(month, m.m)
or
datepart(year, yt.DateOpened) = DATEPART(year, m.m)
and datepart(month, yt.DateOpened) = DATEPART(month, m.m)
)
AND year(coalesce(yt.DateClosed, m.m)) = 2010
order by yt.DateClosed
So above query does not return all months. But if I change above WHERE lines to:
FROM [PROC].ALL_AUDIT
WHERE
BTK_CLOSED_DATE < #TheMonthLastDate
then this query does return all 12 months. How can this be?
Output that I want and that I see when WHERE is BTK_CLOSED_DATE < #TheMonthLastDate:
r2 r3 MonthClosed YearClosed
NULL 2010-06-01 00:00:00.000 6 2010
NULL 2009-11-01 00:00:00.000 11 2009
2010-01-06 20:02:19.127 2010-01-01 00:00:00.000 1 2010
2010-01-27 23:13:45.570 2010-01-01 00:00:00.000 1 2010
2010-02-15 14:49:14.427 2010-02-01 00:00:00.000 2 2010
2010-02-15 14:49:14.427 2009-12-01 00:00:00.000 2 2010
But if I instead use WHERE:
(BTK_CLOSED_DATE < #TheMonthLastDate OR
TSK_START_DATE < #TheMonthLastDate
)
then I see:
r2 r3 MonthClosed YearClosed
NULL 2010-10-01 00:00:00.000 10 2010
NULL 2010-09-01 00:00:00.000 9 2010
NULL 2010-09-01 00:00:00.000 9 2010
NULL 2010-08-01 00:00:00.000 8 2010
NULL 2010-08-01 00:00:00.000 8 2010
...
So notice that in first result I see NULL for June 2010, which is what I want.
I think the problem has something to do with the fact that my data contains 2009-2011 data, but I only compare months and not years. How would I add in this additional logic?
The only place where you are reducing the data is with the WHERE clause you have already identified. Therefore, the reason you are not getting all the months you expect is down to the column TSK_START_DATE not being less than #TheMonthLastDate for all months.
To prove this hypothesis, run the following section of your query (I have commented out part of the where clause and removed everything under 'yourTable' cte). The results should show you what is being returned in the TSK_Start_Date column for your missing months and help you identify why the rows are missing when applying the < #TheMonthLastDate clause.
DECLARE #END_YEAR VARCHAR(10)
DECLARE #END_MONTH VARCHAR(10)
SET #END_YEAR = '2010'
SET #END_MONTH = '10'
DECLARE #TheMonthLastDate DATETIME
DECLARE #TempDate DATETIME
SET #TempDate = '2010-11-01 00:00:00.000'
SET #TheMonthLastDate = '2010-11-01 00:00:00.000'
;with months
as
(
select dateadd(month, -1, dateadd(day, datediff(day, 0, #TempDate), 0)) as m
union all
select dateadd(month, -1, m)
from months
where m > dateadd(month, -12, #TempDate)
)
,yourTable(DateOpened, DateClosed)
as
(select TSK_START_DATE, BTK_CLOSED_DATE
FROM [PROC].ALL_AUDIT
WHERE
(BTK_CLOSED_DATE < #TheMonthLastDate OR
--TSK_START_DATE < #TheMonthLastDate
)
)
select * , #TheMonthLastDate TheMonthLastDate from yourTable