I have a very specific problem in T-SQL.
If I can solve this example case I give you I think I will be able to solve my original case.
Having this data in a table:
DECLARE #Test TABLE
(
Value INT
,Date DATETIME2(7)
);
INSERT INTO #Test
VALUES
(NULL, '2011-01-01 10:00'),
(NULL, '2011-01-01 11:00'),
(2, '2011-01-01 12:00'),
(NULL, '2011-01-01 13:00'),
(3, '2011-01-01 14:00'),
(NULL, '2011-01-01 15:00'),
(NULL, '2011-01-01 16:00'),
(4, '2011-01-01 17:00'),
(NULL, '2011-01-01 18:00'),
(5, '2011-01-01 19:00'),
(6, '2011-01-01 20:00')
I need to select this output:
Value Date
2 2011-01-01 10:00
2 2011-01-01 11:00
2 2011-01-01 12:00
2 2011-01-01 13:00
3 2011-01-01 14:00
3 2011-01-01 15:00
3 2011-01-01 16:00
4 2011-01-01 17:00
4 2011-01-01 18:00
5 2011-01-01 19:00
6 2011-01-01 20:00
To give some explanation. If value is NULL somewhere I need to update with the value from the previous hour. If there are several null values in a row the closest earlier hour with a non null value propagates and fills all these null values. Also if the first hour of the day is null then the earliest hour on the day with a non null value propagates downwards like 2 in this case. In your case you can assume that at least one value is non null value.
My ambition is to solve this with Common table expressions or something similar. With the cursor way I think I would have the solution in short bit of time if I try but my attempts with CTEs and recursive CTEs have failed so far.
Since your condition is not always the same this is a little bit more difficult. In your example, the first two rows need to get their values from the first value with a later date, in the other cases they need to get the values from previous dates. If you would always need to look previous dates, you could simple do this query:
SELECT B.Value,
A.[Date]
FROM #Test A
OUTER APPLY (SELECT TOP 1 *
FROM #Test
WHERE [Date] <= A.[Date] AND Value IS NOT NULL
ORDER BY [Date] DESC) B
But in your case, I think that you need this instead:
SELECT ISNULL(B.Value,C.Value) Value,
A.[Date]
FROM #Test A
OUTER APPLY (SELECT TOP 1 *
FROM #Test
WHERE [Date] <= A.[Date] AND Value IS NOT NULL
ORDER BY [Date] DESC) B
OUTER APPLY (SELECT TOP 1 *
FROM #Test
WHERE Value IS NOT NULL
ORDER BY [Date]) C
try this:
select
t.value, t.date
,COALESCE(t.value
,(select MAX(tt.value) from #Test tt WHERE t.Date>tt.Date)
,(SELECT MIN(ttt.Value) FROM #Test ttt Where ttt.Date IS NOT NULL)
) AS UseValue
from #Test t
OUTPUT:
value date UseValue
----------- ----------------------- -----------
NULL 2011-01-01 10:00:00.000 2
NULL 2011-01-01 11:00:00.000 2
2 2011-01-01 12:00:00.000 2
NULL 2011-01-01 13:00:00.000 2
3 2011-01-01 14:00:00.000 3
NULL 2011-01-01 15:00:00.000 3
NULL 2011-01-01 16:00:00.000 3
4 2011-01-01 17:00:00.000 4
NULL 2011-01-01 18:00:00.000 4
5 2011-01-01 19:00:00.000 5
6 2011-01-01 20:00:00.000 6
Related
Hoping someone has run across this issue previously and has a solution.
I am trying to find customers who lapse based off subscription periods rather than a single order date.
Lapse is defined by us as not making a purchase/renewal within 30 days of the end of their subscription. A customer can have multiple subscriptions simultaneously and subscriptions can vary in length.
I have a data set that includes customerIDs, Orders, the subscription start date, the subscription expire date, and that order’s rank in the customer’s order history, something like this:
CREATE TABLE #Subscriptions
(CustomerID INT,
Orderid INT,
SubscriptionStart DATE,
SubscriptionEnd DATE,
OrderNumber INT);
INSERT INTO #Subscriptions
VALUES(1, 111111, '2017-01-01', '2017-12-31', 1),
(1, 211111, '2018-01-01', '2019-12-31' ,2),
(1, 311121, '2018-10-01', '2018-10-02', 3),
(1, 451515, '2019-02-01', '2019-02-28', 4),
(2, 158797, '2018-07-01', '2018-07-31', 1),
(2, 287584, '2018-09-01', '2018-12-31', 2),
(2, 387452, '2019-01-01', '2019-01-31', 3),
(3, 187498, '2019-01-01', '2019-02-28', 1),
(3, 284990, '2019-02-01', '2019-02-28', 2),
(4, 184849, '2019-02-01', '2019-02-28', 1)
Within this data set, customer 2 would have lapsed on 2018-07-31. Since Customer 1 has a subscription of 2017-01-01 - 2017-12-31 and then one that starts 2018-01-01 and ends 2019-12-31 they cannot lapse within that time period even if other orders made by the customer would qualify.
I have attempt some of simple gap calculations using LEAD() and LAG(), however, I have had no success due to the variable lengths of the subscription period where a single subscription can span across multiple other orders. Eventually, we will use this to calculate monthly churn rate across approximately 5 million records.
You're overthinking this trying to use LEAD() and LAG(). All you need is a NOT EXISTS() function in the WHERE clause
In psuedocode:
SELECT...FROM...
WHERE {SubscriptionEnd is at least 30 days in the past}
AND NOT EXISTS(
{A row for the same Customer where the StartDate is 30 days or less after this EndDate}
)
This one looks to be a tricky one. You are correct about the problem with using the LEAD() and LAG() functions. It stems from customers being able to have multiple subscriptions of variable length. So we need to deal with that issue first. Let's begin with creating a single list of dates instead of having a list of SubscriptionStart and SubscriptionEnd.
SELECT
CustomerId,
OrderId,
1 AS Activity,
SubscriptionStart AS ActivityDate
FROM
#Subscriptions
UNION ALL
SELECT
CustomerId,
OrderId,
-1 AS Activity,
SubscriptionEnd AS ActivityDate
FROM
#Subscriptions
ORDER BY
CustomerId,
ActivityDate
CustomerId OrderId Activity ActivityDate
----------- ----------- ----------- ------------
1 111111 1 2017-01-01
1 111111 -1 2017-12-31
1 211111 1 2018-01-01
1 311121 1 2018-10-01
1 311121 -1 2018-10-02
1 451515 1 2019-02-01
1 451515 -1 2019-02-28
1 211111 -1 2019-12-31
2 158797 1 2018-07-01
2 158797 -1 2018-07-31
2 287584 1 2018-09-01
2 287584 -1 2018-12-31
2 387452 1 2019-01-01
2 387452 -1 2019-01-31
3 187498 1 2019-01-01
3 284990 1 2019-02-01
3 187498 -1 2019-02-28
3 284990 -1 2019-02-28
4 184849 1 2019-02-01
4 184849 -1 2019-02-28
Notice the additional Activity field. It is 1 for the SubscriptionStart and -1 for the SubscriptionEnd.
Using this new Activity field it is possible to find places where there might be a lapse in the customer's subscriptions. At the same time use LEAD() to find the NextDate.
;WITH SubscriptionList AS (
SELECT
CustomerId,
OrderId,
1 AS Activity,
SubscriptionStart AS ActivityDate
FROM
#Subscriptions
UNION ALL
SELECT
CustomerId,
OrderId,
-1 AS Activity,
SubscriptionEnd AS ActivityDate
FROM
#Subscriptions
)
SELECT
CustomerId,
OrderId,
Activity,
SUM(Activity) OVER(PARTITION BY CustomerId ORDER BY ActivityDate ROWS UNBOUNDED PRECEDING) as SubscriptionCount,
ActivityDate,
LEAD(ActivityDate, 1, GETDATE()) OVER(PARTITION BY CustomerId ORDER BY ActivityDate) AS NextDate,
DATEDIFF(d, ActivityDate, LEAD(ActivityDate, 1, GETDATE()) OVER(PARTITION BY CustomerId ORDER BY ActivityDate)) AS LapsedDays
FROM
SubscriptionList
ORDER BY
CustomerId,
ActivityDate
CustomerId OrderId Activity SubscriptionCount ActivityDate NextDate LapsedDays
----------- ----------- ----------- ----------------- ------------ ---------- -----------
1 111111 1 1 2017-01-01 2017-12-31 364
1 111111 -1 0 2017-12-31 2018-01-01 1
1 211111 1 1 2018-01-01 2018-10-01 273
1 311121 1 2 2018-10-01 2018-10-02 1
1 311121 -1 1 2018-10-02 2019-02-01 122
1 451515 1 2 2019-02-01 2019-02-28 27
1 451515 -1 1 2019-02-28 2019-12-31 306
1 211111 -1 0 2019-12-31 2019-02-28 -306
2 158797 1 1 2018-07-01 2018-07-31 30
2 158797 -1 0 2018-07-31 2018-09-01 32
2 287584 1 1 2018-09-01 2018-12-31 121
2 287584 -1 0 2018-12-31 2019-01-01 1
2 387452 1 1 2019-01-01 2019-01-31 30
2 387452 -1 0 2019-01-31 2019-02-28 28
3 187498 1 1 2019-01-01 2019-02-01 31
3 284990 1 2 2019-02-01 2019-02-28 27
3 187498 -1 1 2019-02-28 2019-02-28 0
3 284990 -1 0 2019-02-28 2019-02-28 0
4 184849 1 1 2019-02-01 2019-02-28 27
4 184849 -1 0 2019-02-28 2019-02-28 0
Adding running total on the Activity field will effectively give the number of active subscriptions. While it is greater than 0 a lapse is not possible. So focus in on the rows WHERE the SubscriptionCount is zero.
Using LEAD() get the NextDate. If there isn't a next date then default to today. If the SubscriptionCount is 0 then the NextDate has to be from a new subscription and the NextDate will be the date that the new subscription starts. Using DATEDIFF count the number of days between the SubscriptionEnd and the SubscriptionBegin if it is > 30 days then there was a lapse. Sounds like a good WHERE statement.
;WITH SubscriptionList AS (
SELECT
CustomerId,
OrderId,
1 AS Activity,
SubscriptionStart AS ActivityDate
FROM
#Subscriptions
UNION ALL
SELECT
CustomerId,
OrderId,
-1 AS Activity,
SubscriptionEnd AS ActivityDate
FROM
#Subscriptions
)
, FindLapse AS (
SELECT
CustomerId,
OrderId,
Activity,
SUM(Activity) OVER(PARTITION BY CustomerId ORDER BY ActivityDate ROWS UNBOUNDED PRECEDING) as SubscriptionCount,
ActivityDate,
LEAD(ActivityDate, 1, GETDATE()) OVER(PARTITION BY CustomerId ORDER BY ActivityDate) AS NextDate
FROM
SubscriptionList
)
SELECT
CustomerId,
OrderId,
Activity,
SubscriptionCount,
ActivityDate,
NextDate,
DATEDIFF(d, ActivityDate, NextDate) AS LapsedDays
FROM
FindLapse
WHERE
SubscriptionCount = 0
AND DATEDIFF(d, ActivityDate, NextDate) >= 30
CustomerId OrderId Activity SubscriptionCount ActivityDate NextDate LapsedDays
----------- ----------- ----------- ----------------- ------------ ---------- -----------
2 158797 -1 0 2018-07-31 2018-09-01 32
Looks like we have a winner!
I am working with SQL Server 2012, I have a table with approx 35 column and 10+ million rows.
I need to find time ranges from across the data where the value of any particular column is matching
E.g.
The sample data is as below
Datetime col1 col2 col3
2018-05-31 0:00 1 2 1
2018-05-31 13:00 2 2 2
2018-05-31 14:30 3 2 1
2018-05-31 15:00 4 3 1
2018-05-31 16:00 4 5 1
2018-05-31 17:00 3 2 2
2018-05-31 17:30 3 2 4
2018-05-31 18:00 2 2 4
2018-05-31 20:00 1 2 6
2018-05-31 21:00 2 2 3
2018-05-31 21:10 2 2 1
2018-05-31 22:00 1 6 3
2018-05-31 22:00 4 5 1
2018-05-31 23:59 4 7 2
Find the time range from data where col2 value =< 2, accordingly my expected result set is as below
Start Time End time Time Diff
2018-05-31 0:00 2018-05-31 14:30 14:30:00
2018-05-31 17:00 2018-05-31 21:10 4:10:00
I can achieved the same with below logic, but it's extremely slow
I get all rows and then
Order by date_Time
Scan the rows get the first row where exactly value is matching and record that timestamp as start time.
Scan further rows till i get the row where condition is breaking and record that timestamp as end time.
But as i have to play with huge no. Of rows, overall this will make my operation slow, any inputs or pseudo code to improve the same.
We can use a slightly modified difference in row number method here. The purpose of the first CTE labelled cte1 is to add a computed column which labels islands we want, having a col2 values <= 2, as 1 and everything else as 0. Then, we can compute the difference of two row numbers, and aggregate over the islands to find the starting and ending times, and the difference between those times.
WITH cte1 AS (
SELECT *,
CASE WHEN col2 <= 2 THEN 1 ELSE 0 END AS class
FROM yourTable
),
cte2 AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY Datetime) -
ROW_NUMBER() OVER (PARTITION BY class ORDER BY Datetime) rn
FROM cte1
)
SELECT
MIN(Datetime) AS [Start Time],
MAX(Datetime) AS [End Time],
CONVERT(TIME, MAX(Datetime) - MIN(Datetime)) AS [Time Diff]
FROM cte2
WHERE class = 1
GROUP BY rn
ORDER BY MIN(Datetime);
Demo
I have a SQL query that looks like this.
Select Timestamp, Value From [dbo].[nro_ReadRawDataByTimeFunction](
'SV/SVTP01.BONF0335-D1-W1-BL1',
'2017-11-01 00:00',
'2017-12-01 00:00')
GO
This will return
Timestamp | Value
1 2017-11-01 10:00 | 0
2 2017-11-01 11:00 | 0
3 2017-11-01 12:00 | 0
4 2017-11-01 13:00 | 1
5 2017-11-01 14:00 | 1
6 2017-11-01 15:00 | 0
7 2017-11-01 16:00 | 0
8 2017-11-01 17:00 | 0
9 2017-11-01 18:00 | 1
10 2017-11-01 19:00 | 0
The full list is alot larger, and I'm only interested in in results where value change from last result, so in this case row 1,4,6,9,10
I know how to do it if it's directly from a table but not when it's from a function
You can use this construction:
;WITH cte AS (
Select [Timestamp],
[Value]
From [dbo].[nro_ReadRawDataByTimeFunction](
'SV/SVTP01.BONF0335-D1-W1-BL1',
'2017-11-01 00:00',
'2017-12-01 00:00')
)
SELECT TOP 1 WITH TIES c.*
FROM cte c
OUTER APPLY (
SELECT TOP 1 *
FROM cte
WHERE [Value] != c.[Value] AND c.[Timestamp] < [Timestamp]
ORDER BY [Timestamp] ASC
) t
ORDER BY ROW_NUMBER() OVER (PARTITION BY t.[Timestamp] ORDER BY c.[Timestamp] ASC)
Output:
Timestamp Value
2017-11-01 19:00 0
2017-11-01 10:00 0
2017-11-01 13:00 1
2017-11-01 15:00 0
2017-11-01 18:00 1
Explanation:
SELECT *
FROM cte c
OUTER APPLY (
SELECT TOP 1 *
FROM cte
WHERE [Value] != c.[Value] AND c.[Timestamp] < [Timestamp]
ORDER BY [Timestamp] ASC
) t
Here we select data from main table and with the help of OUTER APPLY add to each row data with different value and greater timestamp.
ROW_NUMBER() OVER (PARTITION BY t.[Timestamp] ORDER BY c.[Timestamp] ASC)
Hope, you are familiar with ROW_NUMBER it
returns the sequential number of a row within a partition of a result set, starting at 1 for the first row in each partition.
So, if you run the above query and add this code to SELECT, you will get:
Timestamp Value Timestamp Value rn
2017-11-01 19:00 0 NULL NULL 1
2017-11-01 10:00 0 2017-11-01 13:00 1 1
2017-11-01 11:00 0 2017-11-01 13:00 1 2
2017-11-01 12:00 0 2017-11-01 13:00 1 3
2017-11-01 13:00 1 2017-11-01 15:00 0 1
2017-11-01 14:00 1 2017-11-01 15:00 0 2
2017-11-01 15:00 0 2017-11-01 18:00 1 1
2017-11-01 16:00 0 2017-11-01 18:00 1 2
2017-11-01 17:00 0 2017-11-01 18:00 1 3
2017-11-01 18:00 1 2017-11-01 19:00 0 1
As you can see, all rows you need are marked with 1. We coluld put this in other CTE or sub-query and use rn = 1 but we can do it all-in-one with the help of TOP 1 WITH TIES (MSDN link).
Since you are referring to SQL Server 2012 you can enjoy the new features:
;WITH Hist AS (
SELECT r,
LAG(v) OVER(ORDER BY d) PreviousValue,
v,
LEAD(v) OVER(ORDER BY d) NextValue ---Just to know that also this is available
FROM #t
)
SELECT *
FROM Hist h Inner JOIN #t t ON h.r = t.r
WHERE ISNULL(h.PreviousValue, -1) != t.v
#t contains your results
If this function is a table-valued function, you can just put it in the where or do it inside a function, in this second case as a parameter?
Select a.Timestamp, a.Value From [dbo].[nro_ReadRawDataByTimeFunction](
'SV/SVTP01.BONF0335-D1-W1-BL1',
'2017-11-01 00:00',
'2017-12-01 00:00') as a
WHERE a.Value = 1
What i ended with is as following
#DECLARE #startDate DATE,
#tagName nVarChar(200);
WITH CTE AS(
SELECT Timestamp As StopTime, Value As [OFF], LAG(Value,1) OVER (order by Timestamp) As [ON], Quality
FROM [dbo].[nrp_ReadRawDataByTimeFunction] (#TagName,#startDate, DATEADD(MONTH,2,#startDate))
Where Quality & 127 = 100
),
CalenderCTE AS(
SELECT [DATE] = DATEADD(Day,Number,#startDate)
FROM master..spt_values
WHERE Type='P'
AND DATEADD(day,Number,#startDate) < DATEADD(MONTH,1,#startDate)
)
SELECT * FROM CTE
FULL OUTER JOIN
CalenderCTE on CalenderCTE.Date = CAST(CTE.StopTime as [Date])
Where CTE.[OFF] != CTE.[ON}
This is just a tiny part of the query since it doing alot more which ain't included in the original post.
Thanks all for your input's, it help me on the way to the final result.
I have a table similar to the one represented below.
myID | some data | start_date | end_date
1 Tom 2016-01-01 2016-05-09
2 Mike 2015-03-01 2017-03-09
...
I have a function that when provided with start_date, end_date, interval (for example weeks)
returns me data as below. (splits the start and end dates to week intervals)
select * from my_function('2016-01-01','2016-01-12', 'ww')
2015-12-28 00:00:00.000 | 2016-01-03 00:00:00.000 15W53
2016-01-04 00:00:00.000 | 2016-01-10 00:00:00.000 16W1
2016-01-11 00:00:00.000 | 2016-01-17 00:00:00.000 16W2
I would like to be able to write a query that returns all of the values from the 1 table, but splits Start date and end date in to multiple rows using the function.
myID | some data | Week_start_date | Week_end_date | (optional)week_num
1 Tom 2015-12-28 2016-01-03 15W53
1 Tom 2016-01-04 2016-01-10 16W1
1 Tom 2016-01-11 2016-01-17 16W2
...
2 Mike etc....
Could someone please help me with creating such a query ?
select myID,some_data,b.Week_start_date,b.Week_end_date,b.(optional)week_num from #a cross apply
(select * from my_function('2016-01-01','2016-01-12', 'ww'))b
like sample data i tried
create table #a
(
myID int, some_data varchar(50) , start_date date, end_date date)
insert into #a values
(1,'Tom','2016-01-01','2016-05-09'),
(2,'Mike','2015-03-01','2017-03-09')
here iam keeping function result into one temp table
create table #b
(
a datetime,b datetime, c varchar(50)
)
insert into #b values
('2015-12-28 00:00:00.000','2016-01-03 00:00:00.000','15W53'),
('2016-01-04 00:00:00.000','2016-01-10 00:00:00.000','16W1 '),
('2016-01-11 00:00:00.000','2016-01-17 00:00:00.000','16W2 ')
select myID,some_data,b.a,b.b,b.c from #a cross apply
(select * from #b)b
output like this
myID some_data a b c
1 Tom 2015-12-28 00:00:00.000 2016-01-03 00:00:00.000 15W53
1 Tom 2016-01-04 00:00:00.000 2016-01-10 00:00:00.000 16W1
1 Tom 2016-01-11 00:00:00.000 2016-01-17 00:00:00.000 16W2
2 Mike 2015-12-28 00:00:00.000 2016-01-03 00:00:00.000 15W53
2 Mike 2016-01-04 00:00:00.000 2016-01-10 00:00:00.000 16W1
2 Mike 2016-01-11 00:00:00.000 2016-01-17 00:00:00.000 16W2
Based on your current result and expected result,the only difference ,i see is myID
so you will need to frame your query like this..
;with cte
as
(
select * from my_function('2016-01-01','2016-01-12', 'ww')
)
select dense_rank() over (order by somedata) as col,
* from cte
Dense Rank assigns same values for the same partition and assigs the sequential value to next partition ,unlike Rank
Look here for more info:
https://stackoverflow.com/a/7747342/2975396
I need to update a foreign key in table 1 with the correct entry based on table 2. The correct foreign key is the earliest date that falls after, but not before the next effective dates in table 2. If there are multiple entries in table 2 with the same effective date, then use the modified date column as a tie breaker and pick the most recent one. Here is the based table structure (all dates are in Date format):
Table 1
pK1 PeriodStartDate pK2
1 2016-04-01 00:00:00.000
2 2016-07-01 00:00:00.000
Table 2
pK2 EffectiveFrom ModifiedDate
3 2016-03-01 00:00:00.000 2016-04-01 00:00:00.000
4 2016-05-01 00:00:00.000 2016-06-01 00:00:00.000
5 2016-05-01 00:00:00.000 2016-06-02 00:00:00.000
So in the above example table 1 would look like this:
pK1 PeriodStartDate pK2
1 2016-04-01 00:00:00.000 3
2 2016-07-01 00:00:00.000 5
This is because for row 1 it falls between March 1st and May 1st (from table 2). And for row 2 it is after the last date, but as there are two similar start dates we choose the last modified.
I'm not sure of the solution. I was trying something like this:
UPDATE table1
SET pK2 = table2.pK2
FROM table2
WHERE PeriodStartDate > (SELECT FIRST(table2.EffectiveFrom) FROM table2)
I'm just not sure how to find an entry that is bounded by another row (and then needs another column for the tie breaker)
First off, you need to apply a row_number() over Table2, partitioned on the PeriodStart and ordered by the ModifiedDate (desc). Call this MaxModified; and 1 is always the most recently modified record.
pK2 PeriodStart ModifiedDate MaxModified
3 2016-03-01 00:00:00.000 2016-04-01 00:00:00.000 1
5 2016-05-01 00:00:00.000 2016-06-02 00:00:00.000 1
4 2016-05-01 00:00:00.000 2016-06-01 00:00:00.000 2
Then, for only where MaxModified=1, you add a new "id" to this so we can line up a start date, with the next rows start date (our end date). This is also done with the row_number() function ordered by the PeriodStart.
pK2 PeriodStart ModifiedDate MaxModified myID
3 2016-03-01 00:00:00.000 2016-04-01 00:00:00.000 1 1
5 2016-05-01 00:00:00.000 2016-06-02 00:00:00.000 1 2
Then we take that result and join it to itself offset by one row to get an end date value for each original row.
pK2 PeriodStart ModifiedDate MaxModified myID PeriodEnd
3 2016-03-01 00:00:00.000 2016-04-01 00:00:00.000 1 1 2016-05-01 00:00:00.000
5 2016-05-01 00:00:00.000 2016-06-02 00:00:00.000 1 2 NULL
Once we have that, its a simple matter of joining on the start/end dates to get our pk2 value.
Full script...
DECLARE #Table1 TABLE (pK1 INT, PeriodStart DATETIME, pK2 INT)
DECLARE #Table2 TABLE (pK2 INT, PeriodStart DATETIME, ModifiedDate DATETIME)
INSERT INTO #Table1
VALUES (1,'2016-04-01',NULL),
(2,'2016-07-01',NULL)
INSERT INTO #Table2
VALUES (3,'2016-03-01','2016-04-01'),
(4,'2016-05-01','2016-06-01'),
(5,'2016-05-01','2016-06-02')
;WITH OrderedList AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY PeriodStart ORDER BY ModifiedDate DESC) AS MaxModified
FROM #Table2
),X AS
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY PeriodStart) AS myID
FROM OrderedList
WHERE MaxModified=1
), Y AS
(
SELECT L.*, R.PeriodStart AS PeriodEnd
FROM X L
LEFT JOIN X R ON L.myID=R.myID-1 AND R.MaxModified=1
WHERE L.MaxModified=1
)
UPDATE T SET pK2=Y.pK2
FROM #Table1 T
LEFT JOIN Y ON T.PeriodStart >= Y.PeriodStart AND T.PeriodStart < COALESCE(Y.PeriodEnd,CURRENT_TIMESTAMP)
SELECT *
FROM #Table1