Update table based on Dates in SQL Server? - sql-server

I got below 2 tables:
if object_id('tempdb..#t1') is not null
drop table #t1
create table #t1
(
ID int,
opendate datetime,
closedate datetime,
[ADDRESS] varchar(50)
)
insert into #t1 (ID, opendate, closedate)
values (111, '1930-05-01 00:00:00.000', '2004-10-23 00:00:00.000'),
(111, '2004-10-23 00:00:00.000', '2006-03-26 00:00:00.000'),
(111, '2006-10-23 00:00:00.000', '2009-03-26 00:00:00.000'),
(111, '2009-03-26 00:00:00.000', '2013-05-21 00:00:00.000'),
(111, '2013-05-21 00:00:00.000', '2013-06-18 00:00:00.000'),
(111, '2013-06-18 00:00:00.000', '2016-04-11 00:00:00.000'),
(111, '2016-04-11 00:00:00.000', '2016-06-16 00:00:00.000'),
(111, '2016-06-16 00:00:00.000', '2016-06-21 00:00:00.000'),
(111, '2016-06-21 00:00:00.000', NULL)
select
*
from
#t1
if object_id('tempdb..#t2') is not null
drop table #t2
create table #t2
(
ID int,
opendate datetime,
closedate datetime,
[ADDRESS] varchar(50)
)
insert into #t2 (ID, opendate, closedate, [ADDRESS])
values
(111,'1930-05-01 00:00:00.000','2004-10-23 00:00:00.000','1 AVENUE' )
,(111,'2004-10-23 00:00:00.000','2009-03-26 00:00:00.000','2 AVENUE' )
,(111,'2009-03-26 00:00:00.000','2013-05-21 00:00:00.000','3 AVENUE' )
,(111,'2013-05-21 00:00:00.000' ,NULL ,'5 AVENUE' )
,(111,'2016-04-11 00:00:00.000' ,'2016-06-16 00:00:00.000','6 AVENUE' )
,(111,'2016-06-16 00:00:00.000' ,NULL ,'7 AVENUE' )
,(111,'2016-06-21 00:00:00.000' ,NULL ,'8 AVENUE' )
select
*
from
#t2
I want update first table like below:
111 1930-05-01 00:00:00.000 2004-10-23 00:00:00.000 '1 AVENUE'
111 2004-10-23 00:00:00.000 2006-03-26 00:00:00.000 '2 AVENUE'
111 2006-03-26 00:00:00.000 2009-03-26 00:00:00.000 '2 AVENUE'
111 2009-03-26 00:00:00.000 2013-05-21 00:00:00.000 '3 AVENUE'
111 2013-05-21 00:00:00.000 2013-06-18 00:00:00.000 '5 AVENUE'
111 2013-06-18 00:00:00.000 2016-04-11 00:00:00.000 '5 AVENUE'
111 2016-04-11 00:00:00.000 2016-06-16 00:00:00.000 '6 AVENUE'
111 2016-06-16 00:00:00.000 2016-06-21 00:00:00.000 '7 AVENUE'
111 2016-06-21 00:00:00.000 NULL '8 AVENUE'
I have tried some ways but it is not returning the correct results because of nulls.
Thanks.

update t1 set address = tmp.address
from (select t1.ID, t1.opendate, ROW_NUMBER() over (partition by t1.opendate order by t2.opendate desc) row, t2.ADDRESS
from #t1 t1
inner join #t2 t2 on t1.ID = t2.ID and t1.opendate between t2.opendate and isnull(t2.closedate, t1.opendate)) tmp
inner join #t1 t1 on t1.ID = tmp.ID and t1.opendate = tmp.opendate and tmp.row = 1

Related

SQL Server check if particular field is updated in a table

I have a table with following structure
UpdateDate Rate
2015-08-26 00:00:00.000 310.000000
2016-06-02 00:00:00.000 310.000000
2017-02-01 00:00:00.000 310.000000
2017-09-15 00:00:00.000 320.000000
2018-01-31 00:00:00.000 310.000000
2018-02-16 00:00:00.000 310.000000
2018-02-23 00:00:00.000 310.000000
2018-03-09 00:00:00.000 310.000000
2018-04-15 00:00:00.000 320.000000
I am passing FromDate as report criteria.
I want to get only those rows in which Rate field is greater than previous rate after FromDate criteria.
For example, if I pass 2018-04-01 as FromDate selection criteria,
then I will get following output becuase on 2018-04-15 rate is greater than that on 2018-03-09
2018-04-15 00:00:00.000 320.000000
But if I pass 2018-05-01 as FromDate parameter, then I will not get any output as
Rate is not updated from 2018-05-01 till today.
Thanks.
This uses window functions:
declare #test table ([UpdateDate] datetime, [Rate] Decimal(9,6))
insert into #test values
('2015-08-26 00:00:00.000', 310.000000)
, ('2016-06-02 00:00:00.000', 310.000000)
, ('2017-02-01 00:00:00.000', 310.000000)
, ('2017-09-15 00:00:00.000', 320.000000)
, ('2018-01-31 00:00:00.000', 310.000000)
, ('2018-02-16 00:00:00.000', 310.000000)
, ('2018-02-23 00:00:00.000', 310.000000)
, ('2018-03-09 00:00:00.000', 310.000000)
, ('2018-04-15 00:00:00.000', 320.000000)
declare #date as date = '2018-04-01'
;with cte1 as (select [UpdateDate], [Rate], iif(lag(Rate) OVER (ORDER BY [UpdateDate]) < [Rate], 1, 0) as [Changed] from #test),
cte2 as (Select top 1 * from cte1 where [UpdateDate] > #date order by [UpdateDate])
select [UpdateDate], [Rate] from cte2 where [Changed] = 1
set #date = '2018-03-01'
;with cte1 as (select [UpdateDate], [Rate], iif(lag(Rate) OVER (ORDER BY [UpdateDate]) < [Rate], 1, 0) as [Changed] from #test),
cte2 as (Select top 1 * from cte1 where [UpdateDate] > #date order by [UpdateDate])
select [UpdateDate], [Rate] from cte2 where [Changed] = 1
Thanks, it's resolved.
SELECT *
FROM (
SELECT *, LAG(Rate, 1, 0) OVER (ORDER BY UpdateDate) AS PrevRate
FROM #TempRate
) A
WHERE Rate > PrevRate

T-SQL - timespan by overlapping datetime columns

I want maximum period of date range that is overlapping each other and if the period is not clashing other date ranges then I want it as it is.
I have this table:
CREATE TABLE [dbo].[table1]
(
[id] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
[StartDate] [datetime] NOT NULL,
[EndDate] [datetime] NOT NULL
)
And their respective values:
INSERT INTO [dbo].[table1]
VALUES (CAST('2013-11-01 00:00:00.000' AS DateTime), CAST('2013-11-10 00:00:00.000' AS DateTime)),
(CAST('2013-11-05 00:00:00.000' AS DateTime), CAST('2013-11-15 00:00:00.000' AS DateTime)),
(CAST('2013-11-10 00:00:00.000' AS DateTime), CAST('2013-11-15 00:00:00.000' AS DateTime)),
(CAST('2013-11-10 00:00:00.000' AS DateTime), CAST('2013-11-25 00:00:00.000' AS DateTime)),
(CAST('2013-11-26 00:00:00.000' AS DateTime), CAST('2013-11-29 00:00:00.000' AS DateTime))
And expected result is:
ID StartDate EndDate
--------------------------------------------------------
1 2013-11-01 00:00:00.000 2013-11-25 00:00:00.000
2 2013-11-26 00:00:00.000 2013-11-29 00:00:00.000
Thanks in advance.
// Edit 1: Thanks.
Works, but there is a new question for breaks in the same table
INSERT INTO [dbo].[table1]
VALUES (CAST('2018-05-03 08:30:00.000' AS DateTime), CAST('2018-05-03 08:45:00.000' AS DateTime)),
(CAST('2018-05-03 08:45:00.000' AS DateTime), CAST('2018-05-03 09:30:00.000' AS DateTime)),
(CAST('2018-05-03 08:45:00.000' AS DateTime), CAST('2018-05-03 11:30:00.000' AS DateTime)),
(CAST('2018-05-03 12:45:00.000' AS DateTime), CAST('2018-05-03 13:00:00.000' AS DateTime)),
(CAST('2018-05-03 14:00:00.000' AS DateTime), CAST('2018-05-03 15:45:00.000' AS DateTime)),
(CAST('2018-05-03 14:15:00.000' AS DateTime), CAST('2018-05-03 15:30:00.000' AS DateTime))
And expected result is:
ID StartDate EndDate
--------------------------------------------------------
1 2018-05-03 08:30:00.000 2018-05-03 11:30:00.000
2 2018-05-03 12:45:00.000 2018-05-03 13:00:00.000
3 2018-05-03 14:00:00.000 2018-05-03 15:45:00.000
Very similar answer, but making use of an index and windowed functions to make the gaps and islands analysis cheaper (faster).
http://sqlfiddle.com/#!18/f19569/3
SELECT
ROW_NUMBER() OVER (ORDER BY MIN(StartDate)),
MIN(StartDate),
MAX(EndDate)
from
(
SELECT
*,
SUM(CASE WHEN PrecedingEndDate >= StartDate THEN 0 ELSE 1 END)
OVER (ORDER BY StartDate, EndDate)
AS GroupID
FROM
(
SELECT
*,
MAX(EndDate)
OVER (ORDER BY StartDate, EndDate
ROWS BETWEEN UNBOUNDED PRECEDING
AND 1 PRECEDING
)
AS PrecedingEndDate
FROM
Table1
)
look_back
)
grouped
GROUP BY
GroupID
This is a form of the gaps and islands problem.
In this case, exists and cumulative sum and group by are the route to the solution:
select row_number() over (order by min(startdate)),
min(startdate), max(enddate)
from (select t1.*, sum(isstart) over (order by startdate) as grp
from (select t1.*,
(case when exists (select 1
from table1 tt1
where tt1.startdate <= t1.enddate and tt1.enddate >= t1.startdate and tt1.id <> t1.id
)
then 0 else 1
end) as isstart
from table1 t1
) t1
) t1
group by grp;

Group up rows based on date overlapping

In a same id, if any of row's effective date and enddate overlaps then we need group it up in a unique id
In below image dategroup is the desired output column
Data is sorted in order by ID asc, EffectiveDate ASC, EndDate Desc
CREATE TABLE #DataTable (id int , EffectiveDate datetime, Enddate Datetime )
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-01 00:00:00.000' AS DateTime), CAST(N'2017-01-11 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-02 00:00:00.000' AS DateTime), CAST(N'2017-01-05 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-03 00:00:00.000' AS DateTime), CAST(N'2017-01-12 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-06 00:00:00.000' AS DateTime), CAST(N'2017-01-09 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (1, CAST(N'2017-01-13 00:00:00.000' AS DateTime), CAST(N'2017-01-19 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (2, CAST(N'2017-02-01 00:00:00.000' AS DateTime), CAST(N'2017-02-11 00:00:00.000' AS DateTime))
GO
INSERT [dbo].#DataTable ([id], [EffectiveDate], [Enddate]) VALUES (2, CAST(N'2017-02-06 00:00:00.000' AS DateTime), CAST(N'2017-02-16 00:00:00.000' AS DateTime))
GO
Try this, Hope it helps. Not the most attractive code but it should work. I may clean it up later if I find some time.
;WITH cte_StepOne as
(
SELECT ROW_NUMBER() OVER (ORDER BY a.[id],
a.[EffectiveDate],
a.[Enddate]) AS SeqNo,
a.[id],
a.[EffectiveDate],
a.[Enddate],
b.[id] AS OverLapID,
b.[EffectiveDate] AS [OverLapEffectiveDate],
b.[Enddate] AS [OverLapEnddate]
FROM ##DataTable a
LEFT JOIN ##DataTable b
ON a.EffectiveDate BETWEEN b.EffectiveDate
AND b.EndDate
AND a.EffectiveDate <> b.EffectiveDate
AND a.EndDate <> b.EndDate --and a.ID <> b.ID
)
,cte_StepTwo AS
(
SELECT SeqNo,
id,
EffectiveDate,
Enddate,
LEAD(OverLapEffectiveDate, 1) OVER (ORDER BY SeqNo) AS LeadValue,LAG(id, 1) OVER (ORDER BY SeqNo) AS LeadValueID,
OverLapID,
OverLapEffectiveDate,
OverLapEnddate
FROM cte_StepOne
)
,cte_Result AS
(
SELECT id,
EffectiveDate,
Enddate,
CASE
WHEN LeadValue = EffectiveDate AND OverLapEffectiveDate IS NULL THEN ID
WHEN OverLapID IS NULL THEN LeadValueID + 1
ELSE OverLapID
END AS OverLapID,
CASE
WHEN LeadValue = EffectiveDate AND OverLapEffectiveDate IS NULL THEN EffectiveDate
ELSE OverLapEffectiveDate
END AS OverLapEffectiveDate,
CASE
WHEN LeadValue = EffectiveDate AND OverLapEffectiveDate IS NULL THEN Enddate
ELSE OverLapEnddate
END AS OverLapEnddate
FROM cte_StepTwo
)
SELECT DISTINCT id,
EffectiveDate,
Enddate,
DENSE_RANK() OVER (ORDER BY ID,OverLapID) AS DateGroup
FROM cte_Result
ORDER BY id,EffectiveDate
Result:
This answer takes the approach of trying to identify records for which the running DateGroup counter should be incremented. Ultimately, we will assign a value of 1 to such records. With this assignment in hand, we can then simply take a cumulative sum to generate the DateGroup.
-- this CTE identifies all new ID records
WITH cte1 AS (
SELECT t.ID, t.EffectiveDate, t.EndDate
FROM
(
SELECT ID, EffectiveDate, EndDate,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY EffectiveDate) rn
FROM yourTable
) t
WHERE t.rn = 1
),
-- this CTE identifies all records whose both effective and end dates
-- do not fall within the range of the start ID record
cte2 AS (
SELECT t1.ID, t1.EffectiveDate, t1.EndDate
FROM yourTable t1
INNER JOIN cte1 t2
ON t1.ID = t2.ID AND
t1.EffectiveDate NOT BETWEEN t2.EffectiveDate AND t2.EndDate AND
t1.EndDate NOT BETWEEN t2.EffectiveDate AND t2.EndDate
),
-- this CTE returns the original table with a new column, amount, which
-- contains a value of 1 should that record cause the DateGroup to be
-- incremented by 1
cte3 AS (
SELECT t1.ID, t1.EffectiveDate, t1.EndDate,
CASE WHEN t2.ID IS NOT NULL OR t3.ID IS NOT NULL THEN 1 ELSE 0 END AS amount
FROM yourTable t1
LEFT JOIN cte1 t2
ON t1.ID = t2.ID AND
t1.EffectiveDate = t2.EffectiveDate AND
t1.EndDate = t2.EndDate
LEFT JOIN cte2 t3
ON t1.ID = t3.ID AND
t1.EffectiveDate = t3.EffectiveDate AND
t1.EndDate = t3.EndDate
)
-- finally, take a cumulative sum of the 'amount' column to generate the DateGroup
SELECT t1.ID,
t1.EffectiveDate,
t1.EndDate,
SUM(t2.amount) AS DateGroup
FROM cte3 t1
INNER JOIN cte3 t2
ON t1.ID >= t2.ID AND
t1.EffectiveDate >= t2.EffectiveDate
GROUP BY t1.id, t1.EffectiveDate, t1.EndDate;
Output:
Demo here:
Rextester
Data used:
CREATE TABLE yourTable (ID int, EffectiveDate datetime, EndDate datetime);
INSERT INTO yourTable
VALUES
(1, '2017-01-01 00:00:00.000', '2017-01-11 00:00:00.000'),
(1, '2017-01-02 00:00:00.000', '2017-01-05 00:00:00.000'),
(1, '2017-01-03 00:00:00.000', '2017-01-12 00:00:00.000'),
(1, '2017-01-06 00:00:00.000', '2017-01-09 00:00:00.000'),
(1, '2017-01-13 00:00:00.000', '2017-01-19 00:00:00.000'),
(2, '2017-02-01 00:00:00.000', '2017-02-11 00:00:00.000'),
(2, '2017-02-06 00:00:00.000', '2017-02-16 00:00:00.000');
What about this? It's simpler that other solutions posted:
WITH
CTE_GetFirstRecordForEachId AS
(
SELECT
id,
EffectiveDate,
Enddate,
rn = ROW_NUMBER() OVER (PARTITION BY id ORDER BY EffectiveDate, EndDate)
FROM
#DataTable
),
CTE_GetOutOfDateRange AS
(
SELECT
a.*,
OutOfDateRange =
CASE WHEN (b.EffectiveDate>=a.EffectiveDate AND b.EffectiveDate<=b.Enddate) OR (b.Enddate>=a.EffectiveDate AND b.Enddate<=b.Enddate)
THEN 0
ELSE 1
END
FROM
#DataTable a
INNER JOIN
CTE_GetFirstRecordForEachId b ON a.id = b.id AND b.rn=1
)
SELECT
id,
Effectivedate,
Enddate,
DateGroup = DENSE_RANK() OVER (ORDER BY id, OutOfDateRange)
FROM
CTE_GetOutOfDateRange
ORDER BY
id, Effectivedate, Enddate
Output:
id Effectivedate Enddate DateGroup
----------- ----------------------- ----------------------- --------------------
1 2017-01-01 00:00:00.000 2017-01-11 00:00:00.000 1
1 2017-01-02 00:00:00.000 2017-01-05 00:00:00.000 1
1 2017-01-03 00:00:00.000 2017-01-12 00:00:00.000 1
1 2017-01-06 00:00:00.000 2017-01-09 00:00:00.000 1
1 2017-01-13 00:00:00.000 2017-01-19 00:00:00.000 2
2 2017-02-01 00:00:00.000 2017-02-11 00:00:00.000 3
2 2017-02-06 00:00:00.000 2017-02-16 00:00:00.000 3
What about this (I am still testing it)
WITH Z AS
(SELECT * FROM (SELECT ID, [EffectiveDate], ENDDate
, LAG(ID) OVER (PARTITION BY ID ORDER BY EffectiveDate, ENDDate Desc) AS ID_Prec
, LAG(EffectiveDate) OVER (PARTITION BY ID ORDER BY EffectiveDate, ENDDate Desc) AS EffDate_Prec
, LAG(ENDDate) OVER (PARTITION BY ID ORDER BY EffectiveDate, ENDDate Desc) AS EndDate_Prec
, ROW_NUMBER() OVER (ORDER BY ID, EffectiveDate,ENDDate DESC) AS RN
, 1 AS DATEGROUP
FROM #DataTable ) C WHERE RN = 1
UNION ALL
SELECT A.ID, A.EffectiveDate, A.Enddate
, A.ID_Prec, A.EffDate_Prec
, A.EndDate_Prec
, A.RN
, CASE WHEN A.ID = A.ID_PREC AND (A.EffectiveDate <=A.EndDate_Prec /* OR A.EndDate>=A.EffDate_Prec*/) THEN Z.DATEGROUP
ELSE Z.DATEGROUP+1 END AS DATEGROUP
FROM (SELECT A.ID, A.EffectiveDate, A.ENDDate
, LAG(A.ID) OVER (PARTITION BY A.ID ORDER BY A.EffectiveDate, A.ENDDate Desc) AS ID_Prec
, LAG(A.EffectiveDate) OVER (PARTITION BY A.ID ORDER BY A.EffectiveDate, A.ENDDate Desc) AS EffDate_Prec
, LAG(A.ENDDate) OVER (PARTITION BY A.ID ORDER BY A.EffectiveDate, A.ENDDate Desc) AS EndDate_Prec
, ROW_NUMBER() OVER (ORDER BY A.ID, A.EffectiveDate,A.ENDDate DESC) AS RN
, 1 AS DATEGROUP
FROM #DataTable A) A
INNER JOIN Z ON A.RN -1= Z.RN
)
SELECT ID, EffectiveDate, Enddate, DATEGROUP FROM Z
Output:
ID EffectiveDate Enddate DATEGROUP
----------- ----------------------- ----------------------- -----------
1 2017-01-01 00:00:00.000 2017-01-11 00:00:00.000 1
1 2017-01-02 00:00:00.000 2017-01-05 00:00:00.000 1
1 2017-01-03 00:00:00.000 2017-01-12 00:00:00.000 1
1 2017-01-06 00:00:00.000 2017-01-09 00:00:00.000 1
1 2017-01-13 00:00:00.000 2017-01-19 00:00:00.000 2
2 2017-02-01 00:00:00.000 2017-02-11 00:00:00.000 3
2 2017-02-06 00:00:00.000 2017-02-16 00:00:00.000 3
guess you are missing some test scenario in your sample date.
;with CTE as
(
select *,ROW_NUMBER()over(order by id, effectivedate)rn
from #DataTable
)
,CTE1 AS
(
select *, 1 New_ID
from cte
where rn=1
union ALL
select c.id,c.effectivedate,c.enddate,c.rn
,case when c.effectivedate between c1.effectivedate
and c1.enddate
and c.id=c1.id then c1.New_ID
else c1.New_ID+1
END
from cte c
inner join cte1 c1
on c.rn=c1.rn+1
and c.rn>1 and c.rn<=7
)
select * from cte1
drop table #DataTable
this may help you. I posted here shortest and simplest version of tsql...
WITH CTE AS (
SELECT *,
ISNULL(LAG(EffectiveDate) OVER (PARTITION BY id ORDER BY id,EffectiveDate,Enddate),EffectiveDate) AS PreviousEffDate,
ISNULL(LAG(Enddate) OVER (PARTITION BY id ORDER BY id,EffectiveDate,Enddate),Enddate) AS PreviousEndDate
FROM #DataTable)
SELECT id,
EffectiveDate,
Enddate,
DENSE_RANK() OVER (ORDER BY id,CASE
WHEN EffectiveDate BETWEEN PreviousEffDate AND PreviousEndDate OR
Enddate BETWEEN PreviousEffDate AND PreviousEndDate OR
PreviousEffDate BETWEEN EffectiveDate AND Enddate OR
PreviousEndDate BETWEEN EffectiveDate AND Enddate
THEN 0
ELSE 1
END) AS DateGroup
FROM CTE
Result:
Have got this one from another forum; altered as per my requirement . looks simple and effective.
WITH C1 AS (
SELECT *,
CASE WHEN EffectiveDate <= MAX(ISnull(EndDate,'9999-12-31 00:00:00.000')) OVER(partition by id ORDER BY EffectiveDate ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) THEN 0 ELSE 1 END AS isstart
FROM #DataTable
)
SELECT ID,EffectiveDate,EndDate,
SUM(isstart) OVER(ORDER BY ID ROWS UNBOUNDED PRECEDING) AS DG
FROM C1

Return previous record for holiday/weekends

A table has stock name, values and effective date for the stocks and there wont be any entry for holidays/weekends. I would like to return a previous date record if I pass the range of dates contains holidays/weekends.
E.G.
Table name: Stock
ID Name Value EffectiveDate
1 IBM 200.0000 2015-12-31 00:00:00.000
2 IBM 201.4500 2016-01-04 00:00:00.000
3 IBM 201.0000 2016-01-05 00:00:00.000
4 IBM 202.0000 2016-01-06 00:00:00.000
SELECT Name, Value, EffectiveDate FROM Stock WHERE Name = 'IBM' AND EffectiveDate >= '20151231' AND EffectiveDate <= '20160105'
The above query returns top 3 records but I would like to return the below results:
Name Value EffectiveDate ActualDate
IBM 200.0000 2015-12-31 2015-12-31
IBM 200.0000 2015-12-31 2016-01-01
IBM 200.0000 2015-12-31 2016-01-02
IBM 200.0000 2015-12-31 2016-01-03
IBM 201.4500 2016-01-04 2015-01-04
IBM 201.0000 2016-01-05 2015-01-05
01/01/2016 to 03/01/2016 are holidays/weekends. I've a function which returns the previous date if I pass the holiday/weekend date. Could anyone help to write the query in SQL Server to achieve the above?
DECLARE #table TABLE (
id int,
name varchar(20),
value decimal(10, 4),
EffectiveDate datetime
)
INSERT INTO #table
VALUES (1, 'IBM', 200.0000, '2015-12-31 00:00:00.000')
, (2, 'IBM', 201.4500, '2016-01-04 00:00:00.000')
, (3, 'IBM', 201.0000, '2016-01-05 00:00:00.000')
, (4, 'IBM', 202.0000, '2016-01-06 00:00:00.000')
DECLARE #MinDate datetime = '20151231',
#MaxDate datetime = '20160105';
WITH Dates AS (
SELECT #MinDate AS ActualDate
UNION ALL
SELECT DATEADD(day, 1, ActualDate)
FROM Dates
WHERE ActualDate < #MaxDate
)
SELECT [Table].name
,[Table].value
,[Table].EffectiveDate
,[Dates].ActualDate
FROM Dates
CROSS APPLY (
SELECT MAX(EffectiveDate) AS LastEffectiveDate
FROM #table AS [Table]
WHERE [Table].EffectiveDate <= Dates.ActualDate
) AS CA1
INNER JOIN #table AS [Table]
ON [Table].EffectiveDate = CA1.LastEffectiveDate
The following code will return an ActualDate as required for the previous date if the EffectiveDate is a Weekend, however to include logic for bank holidays you will need to define these in a table and then add more logic to the below.
SELECT Name, Value, EffectiveDate,
CASE WHEN datename(dw,EffectiveDate) = 'Saturday'
THEN DATEADD(DAY, -1, EffectiveDate)
WHEN datename(dw,EffectiveDate) = 'Sunday'
THEN DATEADD(DAY, -2, EffectiveDate)
END AS ActualDate
FROM Stock
WHERE Name = 'IBM'
AND EffectiveDate BETWEEN '20151231' AND '20160105'
This might not be a perfect solution but this does the trick.
DECLARE #table TABLE (
id int,
name varchar(20),
value decimal(10, 4),
EffectiveDate datetime
)
INSERT INTO #table
VALUES (1, 'IBM', 200.0000, '2015-12-31 00:00:00.000')
, (2, 'IBM', 201.4500, '2016-01-04 00:00:00.000')
, (3, 'IBM', 201.0000, '2016-01-05 00:00:00.000')
, (4, 'IBM', 202.0000, '2016-01-06 00:00:00.000')
DECLARE #MinDate datetime = '20151231',
#MaxDate datetime = '20160105';
SELECT
id,
(CASE
WHEN Data.Value IS NULL THEN (SELECT TOP (1)
value
FROM #table AS T1
WHERE T1.EffectiveDate < Data.FinalDate
ORDER BY FinalDate DESC)
ELSE Data.value
END),
FinalDate
FROM (SELECT
id,
name,
value,
(CASE
WHEN EffectiveDate IS NULL THEN Date
ELSE EffectiveDate
END) AS FinalDate
FROM #table T
FULL OUTER JOIN (SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b) H
ON T.EffectiveDate = H.Date) Data
ORDER BY finaldate

TSQL - Find the 1st or 2nd day of the week

I have a table of stock prices and need to get prices for the 1st day of each week. This SQL in the WHERE clause works well,
DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
except when the market is closed on Monday. Labor Day is a good example. I thought using COALESCE would give me the price on Tuesday if one were unavailable for Monday, but this didn't work.
coalesce(DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0), DATEADD(ww, DATEDIFF(ww,0,PriceDt), 1)).
Can someone help with this?
declare #t table (PriceDt datetime, Symbol nvarchar(10), OpenPric float, ClosePrice float)
insert #t values ('2010-08-02 00:00:0.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-09 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-16 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-23 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-08-30 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-07 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-13 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-20 00:00:00.000', 'SYM', 15.00, 15.10)
insert #t values ('2010-09-27 00:00:00.000', 'SYM', 15.00, 15.10)
select * from #t
where PriceDt = coalesce(DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0), DATEADD(ww, DATEDIFF(ww,0,PriceDt), 1))
(missing 2010-09-07 00:00:00.000 in the result)
2010-08-02 00:00:00.000 SYM 15 15.1
2010-08-09 00:00:00.000 SYM 15 15.1
2010-08-16 00:00:00.000 SYM 15 15.1
2010-08-23 00:00:00.000 SYM 15 15.1
2010-08-30 00:00:00.000 SYM 15 15.1
2010-09-13 00:00:00.000 SYM 15 15.1
2010-09-20 00:00:00.000 SYM 15 15.1
2010-09-27 00:00:00.000 SYM 15 15.1
This will give you the earliest date that exists in the table for each week (assuming that your week starts on Monday):
select min(Pricedt) Pricedt
from #t
group by DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
Now you can just join that result to your table to get the prices for whatever is the first day of the week that has data entered:
select t.Pricedt, t.Symbol, t.OpenPric, t.ClosePrice
from
(
select min(Pricedt) Pricedt
from #t
group by DATEADD(ww, DATEDIFF(ww,0,PriceDt), 0)
) d
join #t t on d.Pricedt = t.PriceDt

Resources