How to find the last value depends on an event value - sql-server

I have the date who likes :
Key
DATE
Event_Date
Event
LastEventDate
1
2021-12-01
NULL
1
2021-12-02
NULL
1
2021-12-03
NULL
1
2021-12-04
NULL
1
2021-12-05
NULL
1
2021-12-06
2021-12-06
Yes
2021-12-06
1
2021-12-07
NULL
2021-12-06
1
2021-12-08
NULL
2021-12-06
1
2021-12-09
2021-12-09
Yes
2021-12-09
1
2021-12-10
NULL
2021-12-09
1
2021-12-11
NULL
2021-12-09
1
2021-12-12
NULL
2021-12-09
1
2021-12-13
2021-12-13
Yes
2021-12-13
The challenge s to create the red column : LastEventDate.
I tried this
SELECT
Key,
Event_Date,
value_partition,
first_value(Event_Date) over (partition by value_partition order by Key)
FROM (
SELECT
Key,
Event_Date,
sum(case when Event_Date is null then 0 else 1 end) over (order by Key) as value_partition
FROM MyTable
ORDER BY Key ASC
) as A
But I got the error:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
Does anyone has idea how to get the LastEventDate column?

An ORDER BY is only allowed in a subquery when there's a TOP or OFFSET FETCH.
The ORDER BY in the window functions should be on the DATE column.
WITH CTE_DATA AS (
SELECT [Key], [DATE], Event_Date, Event
, SUM(IIF(Event_Date IS NOT NULL,1,0)) OVER (PARTITION BY [Key] ORDER BY [DATE]) AS Rnk
FROM MyTable
)
SELECT [Key], [DATE], Event_Date, Event
, FIRST_VALUE(Event_Date) OVER (PARTITION BY [Key], Rnk ORDER BY [DATE]) AS LastEventDate
FROM CTE_DATA
ORDER BY [Key], [DATE];
GO
Key | DATE | Event_Date | Event | LastEventDate
--: | :--------- | :--------- | :---- | :------------
1 | 2021-12-01 | null | null | null
1 | 2021-12-02 | null | null | null
1 | 2021-12-03 | null | null | null
1 | 2021-12-04 | null | null | null
1 | 2021-12-05 | null | null | null
1 | 2021-12-06 | 2021-12-06 | Yes | 2021-12-06
1 | 2021-12-07 | null | null | 2021-12-06
1 | 2021-12-08 | null | null | 2021-12-06
1 | 2021-12-09 | 2021-12-09 | Yes | 2021-12-09
1 | 2021-12-10 | null | null | 2021-12-09
1 | 2021-12-11 | null | null | 2021-12-09
1 | 2021-12-12 | null | null | 2021-12-09
1 | 2021-12-13 | 2021-12-13 | Yes | 2021-12-13
Test on db<>fiddle here

You can use a running windowed MAX for this
SELECT
t.[Key],
t.DATE,
t.Event_Date,
t.Event,
LastEventDate = MAX(t.Event_Date) OVER (PARTITION BY t.[Key] ORDER BY t.Date ROWS UNBOUNDED PRECEDING)
FROM MyTable t
db<>fiddle

Related

Calculate handling time per user for every ticket based on timestamps and status changes

I have one table.
user_id | ticket_id | status | timestamp
null | 1234 | null | 2022-05-05 01:01:00
null | 1234 | null | 2022-05-05 01:02:00
7461 | 1234 | null | 2022-05-05 01:03:00
null | 1234 | open | 2022-05-05 01:04:00
null | 1234 | null | 2022-05-05 01:06:00
null | 1234 | on hold | 2022-05-05 01:09:00
8474 | 1234 | null | 2022-05-05 01:10:00
null | 1234 | null | 2022-05-05 01:11:00
null | 1234 | solved | 2022-05-05 01:12:00
2638 | 1245 | null | 2022-05-05 01:13:00
null | 1245 | null | 2022-05-05 01:16:00
5721 | 1245 | null | 2022-05-05 01:17:00
I need to find the handling time for each user i.e. the time difference from when a user_id is assigned and the first status change. Also, if there is no status change until the next user is assigned, then I need to find the time difference from when the first user_id is assigned to when the next user_id is assigned.
The output I'm looking for is
user_id | ticket_id | timestamp
7461 | 1234 | 00:01:00
8474 | 1234 | 00:02:00
2638 | 1245 | 00:04:00
Any help would be appreciated.
This is what I've tried and it works in MySQL. But I cannot use MySQL in this case.
SELECT t1.user_id, t1.ticket_id
MIN(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, t1.timestamp, t2.timestamp))) AS timestamp
FROM tab t1
INNER JOIN tab t2
ON t1.timestamp < t2.timestamp
AND t1.user_id IS NOT NULL
AND t2.status IS NOT NULL
GROUP BY t1.user_id, t1.ticket_id
I have used similar query to wht is posted in question, with a few additions -
Used qualify to select top row for each user_id
Ignore rows with NULL timestamp difference.
Used TO_TIME to convert integer value returned by timestampdiff back to time value.
with data_cte (user_id,ticket_id,status,timestamp_1) as
(select * from values
(null,1234,null,'2022-05-05 01:01:00'::timestamp),
(null,1234,null,'2022-05-05 01:02:00'::timestamp),
(7461,1234,null,'2022-05-05 01:03:00'::timestamp),
(null,1234,'open','2022-05-05 01:04:00'::timestamp),
(null,1234,null,'2022-05-05 01:06:00'::timestamp),
(null,1234,'on hold','2022-05-05 01:09:00'::timestamp),
(8474,1234,null,'2022-05-05 01:10:00'::timestamp),
(null,1234,null,'2022-05-05 01:11:00'::timestamp),
(null,1234,'solved','2022-05-05 01:12:00'::timestamp),
(2638,1245,null,'2022-05-05 01:13:00'::timestamp),
(null,1245,null,'2022-05-05 01:16:00'::timestamp),
(5721,1245,null,'2022-05-05 01:17:00'::timestamp)
), cte_1 as
(
select
a.user_id,
a.ticket_id,
a.status,
a.timestamp_1 ts1,
b.timestamp_1 ts2
from data_cte a
left join
data_cte b
on a.timestamp_1 < b.timestamp_1
and
(b.status != nvl(a.status,'0') and b.status is not null)
or
(a.timestamp_1 < b.timestamp_1
and a.user_id != b.user_id)
)
select user_id,
ticket_id,
to_time(''||timestampdiff(second,ts1,ts2)||'') f -- to convert int value from timestampdiff to time.
from cte_1
where user_id is not NULL and f is not NULL
qualify row_number() over (partition by user_id order by f asc) < 2 -- to select top row for each user_id
order by f;
USER_ID
TICKET_ID
F
7461
1234
00:01:00
8474
1234
00:02:00
2638
1245
00:04:00

Snowflake's LAG function bug when current row's value is NULL

I am using LAG function to get "previous" price of the item, and it is working fine, except the case when the current price is NULL, in which case it returns the very last price in the section
SELECT s.id as sale_id,
COALESCE(s.property_id, c.property_id) as c_property_id,
COALESCE(c.sale_date, s.created_at) as sale_date,
c.price,
lag(c.price, 1, NULL) IGNORE NULLS OVER (partition by c_property_id order by sale_date) as previous_price
FROM "CLOSINGS" c FULL OUTER JOIN "SALES" s on c.sale_id = s.id
WHERE c_property_id = xxx
ORDER BY sale_date
And here is what I am getting as a result - please note the 990000 as previous_price on the fourth row
SALE_ID C_PROPERTY_ID SALE_DATE PRICE PREVIOUS_PRICE
xxx 1997-10-06 370000 NULL
xxx 2000-02-22 550000 370000
xxx 2003-09-05 675000 550000
mmmmmmm xxx 2019-11-26 NULL 990000
xxx 2019-12-17 1100000 675000
nnnnnnn xxx 2020-06-16 990000 1100000
I tried to reproduce the issue but as I see, LAG works as expected. Maybe there is something else with your query. Can you share some sample data?
with price_data as (
select * from values
('xxx','1997-10-06',370000 ),
('xxx','2000-02-22',550000 ),
('xxx','2003-09-05',675000 ),
('xxx','2019-11-26',null ),
('xxx','2019-12-17',1100000 ),
('xxx','2020-06-16',990000 ) tmp(C_PROPERTY_ID,SALE_DATE,PRICE))
select C_PROPERTY_ID,SALE_DATE,PRICE,
lag( PRICE, 1, NULL) IGNORE NULLS OVER (partition by c_property_id order by sale_date) as previous_price
FROM price_data
order by sale_date;
+---------------+------------+---------+----------------+
| C_PROPERTY_ID | SALE_DATE | PRICE | PREVIOUS_PRICE |
+---------------+------------+---------+----------------+
| xxx | 1997-10-06 | 370000 | NULL |
| xxx | 2000-02-22 | 550000 | 370000 |
| xxx | 2003-09-05 | 675000 | 550000 |
| xxx | 2019-11-26 | NULL | 675000 |
| xxx | 2019-12-17 | 1100000 | 675000 |
| xxx | 2020-06-16 | 990000 | 1100000 |
+---------------+------------+---------+----------------+

TSQL: Find Groups of Records in a Sequence of Records

Sorry for the title if you find it incorrect, I really wasn't sure how to name this question. There is probably a term for this type of query/pattern.
I have a sequence of records that need to be ordered by date, the records have a condition I would like to "group" by (SomeCondition) to get the earliest start date and latest end date (taking NULL's into account) but I'm unsure how to accomplish the query (if it's even possible). The original records in the table look something like;
-----------------------------------------------------------
| AbcID | XyzID | StartDate | EndDate | SomeCondition |
-----------------------------------------------------------
| 1 | 1 | 2018-01-01 | 2018-03-05 | 1 |
| 2 | 1 | 2018-04-20 | 2018-05-01 | 1 |
| 3 | 1 | 2018-05-02 | 2018-05-15 | 0 |
| 4 | 1 | 2018-06-01 | 2018-07-01 | 1 |
| 5 | 1 | 2018-08-01 | NULL | 1 |
| 6 | 2 | 2018-01-01 | 2018-06-30 | 1 |
| 7 | 2 | 2018-07-01 | 2018-08-31 | 0 |
-----------------------------------------------------------
The result I'm going for would be;
-----------------------------------
| XyzID | StartDate | EndDate |
-----------------------------------
| 1 | 2018-01-01 | 2018-05-01 |
| 1 | 2018-06-01 | NULL |
| 2 | 2018-01-01 | 2018-06-30 |
-----------------------------------
Thanks for any help/insight, even if it's "not possible".
Solving this problem requires you to solve it piece by piece. Here are the steps that I used to do that:
Determine when the island begins (when SomeCondition is false)
Create an "ID" number for each island (within each XyzID) by summing the number of IslandBegins while considering the records in AbcID order
Determine the first and last AbcID within each XyzID/IslandNumber combination where SomeCondition is true
Use the previous step as a guide as to what StartDate / EndDate you should get for each record in the result set
Sample Data:
declare #sample_data table
(
AbcID int
, XyzID int
, StartDate date
, EndDate date
, SomeCondition bit
)
insert into #sample_data
values (1, 1, '2018-01-01', '2018-03-05', 1)
, (2, 1, '2018-04-20', '2018-05-01', 1)
, (3, 1, '2018-05-02', '2018-05-15', 0)
, (4, 1, '2018-06-01', '2018-07-01', 1)
, (5, 1, '2018-08-01', NULL, 1)
, (6, 2, '2018-01-01', '2018-06-30', 1)
, (7, 2, '2018-07-01', '2018-08-31', 0)
Answer:
The comments in the code show which step each part of the CTE is accomplishing.
with island_bgn as
(
--Step 1
select d.AbcID
, d.XyzID
, d.StartDate
, d.EndDate
, d.SomeCondition
, case when d.SomeCondition = 0 then 1 else 0 end as IslandBegin
from #sample_data as d
)
, island_nbr as
(
--Step 2
select b.AbcID
, b.XyzID
, b.StartDate
, b.EndDate
, b.SomeCondition
, b.IslandBegin
, sum(b.IslandBegin) over (partition by b.XyzID order by b.AbcID asc) as IslandNumber
from island_bgn as b
)
, prelim as
(
--Step 3
select n.XyzID
, n.IslandNumber
, min(n.AbcID) as AbcIDMin
, max(n.AbcID) as AbcIDMax
from island_nbr as n
where 1=1
and n.SomeCondition = 1
group by n.XyzID
, n.IslandNumber
)
--Step 4
select p.XyzID
, a.StartDate
, b.EndDate
from prelim as p
inner join #sample_data as a on p.AbcIDMin = a.AbcID
inner join #sample_data as b on p.AbcIDMax = b.AbcID
order by p.XyzID
, a.StartDate
, b.EndDate
Results:
+-------+------------+------------+
| XyzID | StartDate | EndDate |
+-------+------------+------------+
| 1 | 2018-01-01 | 2018-05-01 |
+-------+------------+------------+
| 1 | 2018-06-01 | NULL |
+-------+------------+------------+
| 2 | 2018-01-01 | 2018-06-30 |
+-------+------------+------------+

Compare historical rows (LAG rows) and combine changed values to single column

Compare historical rows (LAG rows based on ResultChngDt) and combine changed column values to single column. Looking for help in writing elegant/efficient SQL Server 2016 TSQL Code(without cursors).
I have a table with the structure and data like this:
+----+-------+--------------+---------------+--------+--------+--------------+
| ID | RepID | CollctedDate | CompletedDate | Result | Tcode | ResultChngDt |
+----+-------+--------------+---------------+--------+--------+--------------+
| 1 | 101 | 11/20/2017 | 12/13/2017 | | L-2190 | 12/13/2017 |
| 1 | 101 | 11/22/2017 | 12/15/2017 | POS | L-Afb | 1/5/2018 |
| 1 | 102 | 11/22/2017 | 12/15/2017 | | L-2191 | 12/15/2017 |
| 1 | 102 | 11/22/2017 | 12/15/2017 | POS | L-2192 | 12/31/2017 |
+----+-------+--------------+---------------+--------+--------+--------------+
I need to generate a report/result as follows:
+----+-------+---------------------------+--------------------------+--+
| ID | RepID | Previous | Current | |
+----+-------+---------------------------+--------------------------+--+
| 1 | 101 | CollctedDate:11/20/2017 | CollctedDate:11/22/2017 | |
| | | CompletedDate:12/13/2017 | CompletedDate:12/15/2017 | |
| | | Result: | Result:POS | |
| | | Tcode:L-2190 | Tcode:L-Afb | |
| 1 | 102 | CollctedDate:11/22/2017 | CollctedDate:11/22/2017 | |
| | | CompletedDate:12/15/2017 | CompletedDate:12/15/2017 | |
| | | Result: | Result:POS | |
| | | Tcode:L-2191 | Tcode:L-2192 | |
+----+-------+---------------------------+--------------------------+--+
CREATE TABLE [dbo].[Table1]
(
[ID] INT NULL,
[RepID] INT NULL,
[CollctedDate] DATETIME NULL,
[CompletedDate] DATETIME NULL,
[Result] VARCHAR(3) NULL,
[Tcode] VARCHAR(10) NULL,
[ResultChngDt] DATETIME NULL
) ON [PRIMARY];
GO
INSERT INTO [dbo].[Table1] ([ID], [RepID], [CollctedDate], [CompletedDate], [Result], [Tcode], [ResultChngDt])
VALUES (1, 101, N'11/20/2017', N'12/13/2017', N'', N'L-2190', N'12/13/2017')
, (1, 101, N'11/22/2017', N'12/15/2017', N'POS', N'L-Afb', N'1/5/2018')
, (1, 102, N'11/22/2017', N'12/15/2017', N'', N'L-2191', N'12/15/2017')
, (1, 102, N'11/22/2017', N'12/15/2017', N'POS', N'L-2192', N'12/31/2017')
Here's my query for your question:
WITH cte_LEADLAG AS(
SELECT ID,
RepID,
CollctedDate,
CompletedDate,
Result,
Tcode,
ResultChngDt,
CONCAT('CollectedDate:',CAST(CollctedDate AS DATETIME2), ' CompletedDate:', CAST(CompletedDate AS DATETIME2), ' Result:', Result, ' Tcode', Tcode) AS dates,
LAG(CollctedDate) OVER(PARTITION BY RepID ORDER BY CollctedDate) AS 'LAGCollectedDate' ,
lead(CollctedDate) OVER(PARTITION BY RepID ORDER BY CollctedDate) AS 'LEADCollectedDate',
LAG(CompletedDate) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LAGCompDate' ,
lead(CompletedDate) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LEADcompDate' ,
LEAD(Result) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LEADResult' ,
LEAD(Tcode) OVER(PARTITION BY RepID ORDER BY CompletedDate) AS 'LEADTcode'
FROM #temp
),
cte_FINAL AS(
SELECT distinct ID,
RepID,
CASE WHEN cte.LAGCollectedDate IS NULL THEN CONCAT('CollectedDate:',CAST(CollctedDate AS DATETIME2), ' CompletedDate:', CAST(CompletedDate AS DATETIME2), ' Result:', Result, ' Tcode', Tcode) end AS 'Previous',
CASE WHEN cte.LEADCollectedDate IS not NULL THEN CONCAT('CollectedDate:',CAST(cte.LEADCollectedDate AS DATETIME2), ' CompletedDate:', CAST(LEADcompDate AS DATETIME2), ' Result:', cte.LEADResult, ' Tcode', cte.LEADTcode) end AS 'Current'
FROM cte_LEADLAG AS cte
WHERE cte.LEADCollectedDate IN (SELECT MAX(LEADCollectedDate) FROM cte_LEADLAG WHERE cte_LEADLAG.RepID = cte.RepID))
)
SELECT *
FROM cte_FINAL;
Result:
with data as (
select *, row_number() over (partition by RepID order by ResultChgDt desc) as rn
from dbo.Table1
)
select
from data as d1 left outer join data as d2 on d2.rn = d1.rn + 1
where d1.rn = 1 -- I suppose you only want the two most recent??
This gives you all the data you need in a single row. You can handle report formatting to suit whatever requirements you have in whatever tool you're using for that.

SQL Server - recursive references (loop, join, insert?)

I would appreciate if you could give me any hints regarding the fastest solution of the following SQL Server challenge:
Let's say I have a table with DATE, CLIENT and his several characteristics in other columns. I need to calculate COLUMN_1 and COLUMN_2 but:
COLUMN_1 uses the client's characteristics as of current DATE and as of previous DATE and COLUMN_1 value from the previous DATE (recursive referencing)
COLUMN_2 additionally uses COLUMN_1 value as of current date (therefore I would like to refer to its final value, not the particular 'case when' that implements the column logic)
How do I replicate this logic most efficiently in SQL Server?
I was thinking about the loop that goes over DATA and for each DATA, joins previous DATA, calculates firstly COLUMN_1, then COLUMN_2 (but how to make sure that the values in COLUMN_1 are accessible for COLUMN_2?)
Regards,
Bart
Without a specific example you we will not be able to tell you which solution would be the most efficient, especially when you are looking for a solution you describe as recursive. You might not need a full recursive solution if you could use window functions instead.
In sql server 2012+ you have access to lead() and lag() which you can use to get the previous and next values for a column based on a partition and order.
select
client
, date
, nextdate = lead(date) over (partition by client order by date)
, prevdate = lag(date) over (partition by client order by date)
, column1 = 'do stuff with lead/lag'
, column2 = 'do stuff with lead/lag'
from t
rextester example: http://rextester.com/FFHU71709
returns:
+--------+------------+------------+------------+------------------------+------------------------+
| client | date | nextdate | prevdate | column1 | column2 |
+--------+------------+------------+------------+------------------------+------------------------+
| 1 | 2017-01-01 | 2017-01-02 | NULL | do stuff with lead/lag | do stuff with lead/lag |
| 1 | 2017-01-02 | 2017-01-03 | 2017-01-01 | do stuff with lead/lag | do stuff with lead/lag |
| 1 | 2017-01-03 | NULL | 2017-01-02 | do stuff with lead/lag | do stuff with lead/lag |
| 2 | 2017-01-02 | 2017-01-04 | NULL | do stuff with lead/lag | do stuff with lead/lag |
| 2 | 2017-01-04 | 2017-01-06 | 2017-01-02 | do stuff with lead/lag | do stuff with lead/lag |
| 2 | 2017-01-06 | NULL | 2017-01-04 | do stuff with lead/lag | do stuff with lead/lag |
+--------+------------+------------+------------+------------------------+------------------------+
One way to simulate lead/lag prior to sql server 2012 is with outer apply()
select
client
, date
, nextdate
, prevdate
, column1 = 'do stuff with lead/lag'
, column2 = 'do stuff with lead/lag'
from t
outer apply (
select top 1 nextdate = i.date
from t i
where i.client = t.client
and i.date > t.date
order by i.date asc
) n
outer apply (
select top 1 prevdate = i.date
from t i
where i.client = t.client
and i.date < t.date
order by i.date desc
) p
rextester demo: http://rextester.com/GGS1299
returns:
+--------+------------+------------+------------+---------------------------------+---------------------------------+
| client | date | nextdate | prevdate | column1 | column2 |
+--------+------------+------------+------------+---------------------------------+---------------------------------+
| 1 | 2017-01-01 | 2017-01-02 | NULL | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
| 1 | 2017-01-02 | 2017-01-03 | 2017-01-01 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
| 1 | 2017-01-03 | NULL | 2017-01-02 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
| 2 | 2017-01-02 | 2017-01-04 | NULL | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
| 2 | 2017-01-04 | 2017-01-06 | 2017-01-02 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
| 2 | 2017-01-06 | NULL | 2017-01-04 | do stuff with nextdate/prevdate | do stuff with nextdate/prevdate |
+--------+------------+------------+------------+---------------------------------+---------------------------------+
For solutions that absolutely require recursion, then you probably need to use a recursive cte.
;with cte as (
-- non recursive cte to add `nextdate` for recursive join
select
t.client
, t.date
, nextdate = x.date
from t
outer apply (
select top 1 i.date
from t i
where i.client = t.client
and i.date > t.date
order by i.date asc
) x
)
, r_cte as (
--anchor rows / starting rows
select
client
, date
, nextdate
, prevDate = convert(date, null)
, column1 = convert(varchar(64),null)
, column2 = convert(varchar(64),null)
from cte t
where not exists (
select 1
from cte as i
where i.client = t.client
and i.date < t.date
)
union all
--recursion starts here
select
c.client
, c.date
, c.nextdate
, prevDate = p.date
, column1 = convert(varchar(64),'do recursive stuff with p.column1')
, column2 = convert(varchar(64),'do recursive stuff with p.column2')
from cte c
inner join r_cte p
on c.client = p.client
and c.date = p.nextdate
)
select *
from r_cte
rextester demo: http://rextester.com/LKH38243
returns:
+--------+------------+------------+------------+-----------------------------------+-----------------------------------+
| client | date | nextdate | prevdate | column1 | column2 |
+--------+------------+------------+------------+-----------------------------------+-----------------------------------+
| 1 | 2017-01-01 | 2017-01-02 | NULL | NULL | NULL |
| 2 | 2017-01-02 | 2017-01-04 | NULL | NULL | NULL |
| 2 | 2017-01-04 | 2017-01-06 | 2017-01-02 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
| 2 | 2017-01-06 | NULL | 2017-01-04 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
| 1 | 2017-01-02 | 2017-01-03 | 2017-01-01 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
| 1 | 2017-01-03 | NULL | 2017-01-02 | do recursive stuff with p.column1 | do recursive stuff with p.column2 |
+--------+------------+------------+------------+-----------------------------------+-----------------------------------+
Reference
Recursive Queries Using Common Table Expressions (cte)
If using SQL2012 or later, look at the features LAG & LEAD
For example, if you want to use the previous row's value in conjunction with this row's value - LAG like this:
DECLARE #T TABLE (DateCol DATETIME, StringCol VARCHAR(10))
INSERT INTO #T (DateCol, StringCol) VALUES ('2017-01-01','A'), ('2017-01-02','B'), ('2017-01-03','C'), ('2017-01-04','D'), ('2017-01-05','E')
SELECT DateCol, StringCol, PreviousRowStringcol = LAG(StringCol,1,NULL) OVER (ORDER BY DateCol) FROM #T

Resources