SQL Server check if particular field is updated in a table - sql-server

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

Related

SQL Server - Split a date range by a given date

I have a table that stores an Id and an effective period indicating when it is active
PortfolioId StartDate EndDate
1 2018-01-01 00:00:00.000 2018-05-31 00:00:00.000
2 2017-01-01 00:00:00.000 2018-05-31 00:00:00.000
I have another table that stores a component related to the above Id and that too has an effective period. Table 2 can have more that on entry for any given entry in table 1.
PortfolioComponentId PortfolioId SplitDate
1 1 2018-02-28 00:00:00.000
2 1 2018-03-31 00:00:00.000
3 2 2017-03-31 00:00:00.000
4 2 2017-09-20 00:00:00.000
5 2 2018-01-15 00:00:00.000
I have a period where I am running the query for
i.e
StartDate : 30-JUN-2017
End date : 15-MAY-2018
I have looking for a result like the below, where data in table 1 is split based on the data from table 2
PortfolioId StartDate EndDate
1 2018-01-01 00:00:00.000 2018-02-28 00:00:00.000
1 2018-03-01 00:00:00.000 2018-03-31 00:00:00.000 - Starts from End date + 1 from the prev row
1 2018-04-01 00:00:00.000 2018-05-15 00:00:00.000
2 2017-06-30 00:00:00.000 2017-09-20 00:00:00.000 - Starts from Seach date [Portfolio component Id 3 ignored as it falls outside of search date range]
2 2017-09-21 00:00:00.000 2018-01-15 00:00:00.000
2 2018-01-16 00:00:00.000 2018-05-15 00:00:00.000 - Ends by seach end date
Data setup - In case it helps
DECLARE #SearchStartDate DATETIME = '30-JUN-2017'
DECLARE #SearchEndDate DATETIME = '15-MAY-2018'
DECLARE #Portfolio TABLE
(
PortfolioId INT PRIMARY KEY IDENTITY(1,1),
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO #Portfolio
SELECT '01-JAN-2018', '31-MAY-2018'
INSERT INTO #Portfolio
SELECT '01-JAN-2017', '31-MAY-2018'
DECLARE #PortfolioComponents TABLE
(
PortfolioComponentId INT PRIMARY KEY IDENTITY(1,1),
PortfolioId INT,
SplitDate DATETIME
)
INSERT INTO #PortfolioComponents
SELECT 1, '28-FEB-2018'
INSERT INTO #PortfolioComponents
SELECT 1, '31-MAR-2018'
INSERT INTO #PortfolioComponents
SELECT 2, '31-MAR-2017'
INSERT INTO #PortfolioComponents
SELECT 2, '20-SEP-2017'
INSERT INTO #PortfolioComponents
SELECT 2, '15-JAN-2018'
SELECT * from #Portfolio
SELECT * from #PortfolioComponents
I believe I have got a result close to what I want, though not
the most ideal approach
the best approach from a performance perspective
DECLARE #Temp TABLE (BenchmarkDate Datetime, ComponentType int, PortfolioId INT)
INSERT INTO #Temp
SELECT StartDate , 1, PortfolioId FROM #Portfolio
UNION
SELECT EndDate , 2, PortfolioId FROM #Portfolio
INSERT INTO #Temp
SELECT SplitDate , 2, PortfolioId FROM #PortfolioComponents
UNION
SELECT SplitDate + 1 , 1, PortfolioId FROM #PortfolioComponents
DECLARE #Results TABLE
(
Id INT IDENTITY(1,1),
StartDate DATETIME,
EndDate DATETIME,
PortfolioId INT
)
INSERT INTO #Results
SELECT rset1.BenchmarkDate [Startdate],
( SELECT MIN(rset2.BenchmarkDate)
FROM #Temp rset2
WHERE rset2.ComponentType = 2
AND rset2.BenchmarkDate > rset1.BenchmarkDate
AND rset1.PortfolioId = rset2.PortfolioId) [Enddate],
rset1.PortfolioId
FROM #Temp rset1
WHERE rset1.ComponentType = 1
ORDER BY rset1.PortfolioId, rset1.BenchmarkDate
SELECT
CASE WHEN (#SearchStartDate BETWEEN StartDate AND EndDate ) THEN #SearchStartDate ELSE StartDate END StartDate,
CASE WHEN (#SearchEndDate BETWEEN StartDate AND EndDate) THEN #SearchEndDate ELSE EndDate END EndDate,
PortfolioId ,
(
SELECT PortfolioComponentId
FROM #PortfolioComponents pc
WHERE
(pc.PortfolioID = r.PortfolioId AND
(DATEADD(d, 1, pc.SplitDate) = r.StartDate ))
) PortfolioComponentId
FROM #Results r
WHERE
(#SearchStartDate < StartDate AND #SearchStartDate < EndDate AND #SearchEndDate > EndDate)
OR
(#SearchEndDate BETWEEN StartDate AND EndDate)
OR
(#SearchStartDate BETWEEN StartDate AND EndDate )

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;

Update table based on Dates in 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

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