how to club as 1 record if employee has worked continuously - sql

how to club as 1 record if employee has worked continuously from feb 1 to feb 15th.
please help me
example
scenario 1.emp who has worked continuously.
empid datebegin dateend
1 2017-02-01 2017-02-05
1 2017-02-06 2017-02-08
1 2017-02-09 2017-02-15
desired O/P: 1 2017-02-01 2017-02-15
scenario2:not worked continuously
empid datebegin dateend
1 2017-02-01 2017-02-05
1 2017-02-07 2017-02-08
1 2017-02-09 2017-02-15
desired O/P:
empid datebegin dateend
1 2017-02-01 2017-02-05
1 2017-02-07 2017-02-15

Use MAX and MIN aggregate funtcions :
CREATE TABLE #Table(empid INT, datebegin DATE , dateend DATE)
INSERT INTO #Table( empid , datebegin , dateend)
SELECT 1,'2017-02-01','2017-02-05' UNION ALL
SELECT 1,'2017-02-06','2017-02-08' UNION ALL
SELECT 1,'2017-02-09','2017-02-15'
SELECT empid , MIN(datebegin) datebegin ,MAX(dateend) dateend
FROM #Table
GROUP BY empid

Using a Common table expression and ROW_NUmber this is possible like so
DECLARE #sample TABLE (empid INT, datebegin DATE, dateend DATE )
INSERT INTO #sample
( empid, datebegin, dateend )
VALUES
(1,'2017-02-01','2017-02-05' )
,(1,'2017-02-06','2017-02-08' )
,(1,'2017-02-09','2017-02-15' )
,(2,'2017-02-01','2017-02-05' )
,(2,'2017-02-07','2017-02-08' )
,(2,'2017-02-09','2017-02-15' )
;WITH cteX
AS(
SELECT
ROW_NUMBER()OVER (ORDER BY S.empid, S.datebegin) 'RN'
, S.empid
, S.datebegin
, S.dateend
FROM #sample S
)
SELECT
S.RN
, S.empid
, S.datebegin
, S.dateend
, DATEDIFF(DAY,S.dateend, S1.datebegin ) 'Diff'
, S1.datebegin
, S1.dateend
FROM cteX S
JOIN
cteX S1 ON S1.empid = S.empid AND S1.RN = S.RN + 1
ORDER BY S.RN
produces following output
empid datebegin dateend
1 2017-02-01 2017-02-15
2 2017-02-01 2017-02-05
2 2017-02-07 2017-02-15

Related

Sql Pivot on time

Table 1: Daily attendance data:
att_date emp_code emp_name in_time out_time
2018-10-21 9999 Test 2018-10-21 08:00:00.000 2018-10-22 06:00:00.000
Table 2: Trnevents
emp_readr_id DT EVENTID
9999 2018-10-24 07:00:00.000 0
9999 2018-10-24 05:00:00.000 0
9999 2018-10-24 03:00:00.000 0
9999 2018-10-23 21:00:00.000 0
9999 2018-10-23 19:00:00.000 0
9999 2018-10-23 06:00:00.000 0
9999 2018-10-22 06:00:00.000 0
9999 2018-10-21 08:00:00.000 0
I used this query to get all times in between in time and out time ,below query works fine but i try to make in row by using pivot. While using pivot out time shows in next row.
declare #tempProcesstable as table(
[id] [nvarchar](200) NULL,
[time_stamp] datetime NULL,
[AccessType] varchar(3) NULL)
insert into #tempProcesstable
select distinct t1.emp_Reader_id, t1.DT,t1.eventid from daily_attendance_data t2 join trnevents t1
on t1.emp_reader_id=t2.emp_reader_id where (CONVERT(VARCHAR(26), t2.att_Date, 23) >=CONVERT(VARCHAR(26), '2018-10-20', 23)
and CONVERT(VARCHAR(26), t2.att_date, 23) <=CONVERT(VARCHAR(26), '2018-10-21', 23))
and
(t1.DT >=t2.in_time
and t1.DT <=t2.out_time)
-- and t1.emp_reader_id=1000
group by t1.emp_Reader_id,t1.dt,t1.eventid order by t1.emp_reader_id,DT asc
; With CheckIns
As (Select Rowemp_reader_id = Row_Number() Over (Partition by id, Cast(time_stamp As Date) Order By time_stamp),
id, time_stamp,
[Date] = Cast(time_stamp As Date),
[Time] = Cast(time_stamp As Time(0))
From #tempProcesstable)
Select Pvt.id,B.emp_name , [Date], CHECK1, CHECK2,Cast(dateadd(ss,datediff(ss,CHECK1,CHECK2),0) As Time(0)) Total1,
CHECK3, CHECK4,Cast(dateadd(ss,datediff(ss,CHECK3,CHECK4),0) As Time(0)) Total2
From (Select id, [Date], [Time],
CHECKNum = 'CHECK' + Cast(Rowemp_reader_id As varchar(11))
From CheckIns) As P
Pivot (Min([Time])
For CheckNum In (Check1, [Check2], Check3, Check4)
) As Pvt
LEFT OUTER JOIN
dbo.employee AS B ON Pvt.id= B.emp_reader_id
My output:
id emp_name Date CHECK1 CHECK2 Total1 CHECK3 CHECK4 Total2
1048 Singh 2018-10-21 07:06:07 17:34:05 10:27:58 NULL NULL NULL
9999 Test 2018-10-21 08:00:00 NULL NULL NULL NULL NULL NULL
9999 Test 2018-10-22 06:00:00 NULL NULL NULL NULL NULL NULL
Expected output:
I want all times between in time and out time in night to morning also.
can any one help me to rectify this.
id emp_name Date CHECK1 CHECK2 Total1 CHECK3 CHECK4 Total2
1048 Singh 2018-10-21 07:06:07 17:34:05 10:27:58 NULL NULL NULL
9999 Test 2018-10-21 08:00:00 06:00:00 NULL NULL NULL NULL NULL
You can try to use ROW_NUMBER() window function make row number by each date.
then use condition aggregate function to do pivot
SELECT emp_readr_id,
emp_name,
[Date],
MAX(CASE WHEN RN = 1 THEN time END) CHECK1,
MAX(CASE WHEN RN = 2 THEN time END) CHECK2,
MAX(CASE WHEN RN = 3 THEN time END) CHECK3,
MAX(CASE WHEN RN = 4 THEN time END) CHECK4
FROM (
SELECT emp_readr_id,
emp_name,
CONVERT(VARCHAR(10),DT,120) 'Date',
ROW_NUMBER() OVER(PARTITION BY CONVERT(VARCHAR(10),DT,120) ORDER BY DT) rn,
CONVERT(VARCHAR(10),DT,108) time
FROM Daily d
JOIN Trnevents t on t.DT between d.in_time and d.out_time
) t1
group by emp_readr_id,
emp_name,
[Date]
sqlifddle

Calculate Employee Timesheet Data

Suppose you have a timeclock table with only a timestamp entry for each timeclock event:
Userid CheckTime
312 2018-05-08 05:52:00
312 2018-05-08 18:06:00
312 2018-05-10 05:55:00
312 2018-05-10 18:00:00
312 2018-05-11 05:58:00
312 2018-05-11 18:00:00
312 2018-05-12 05:35:00
312 2018-05-12 18:00:00
How can I tally the events in SQL Server to show like this?
Day Date In Out Reg OT
Tuesday 5/8/2018 5:52AM 6:06PM 12.00 0.00
Thursday 5/10/2018 5:55AM 6:00PM 12.00 0.00
Friday 5/11/2018 5:58AM 6:00PM 12.00 0.00
Saturday 5/12/2018 5:35AM 6:00PM 12.00 0.42
Plus, we have overnight people who start their shift in one day and carry over to another day.
I'm not sure how to calculate this since it is row based vs column based.
I've tried this...but it doesn't work correctly...
;WITH emp
AS (SELECT [UserID],
[CheckTime],
CAST([CheckTime] AS DATE) AS [Day],
Row_Number()
OVER( PARTITION BY [UserID], CAST([CheckTime] AS DATE)
ORDER BY [UserID], [CheckTime]) AS [RowNumber]
FROM [dbo].[Clock_Data] WHERE CHECKTIME
BETWEEN '2018-05-06' AND '2018-05-13')
SELECT
t1.[UserID],
E.[Last Name]AS [EMPID],
MIN(t1.[CheckTime]) AS [time_in],
MAX(t2.[CheckTime]) AS [time_out],
CAST((SUM(ISNULL(DATEDIFF(ss, t1.[CheckTime],
t2.[CheckTime]) , 0)) / 3600)-1 AS VARCHAR(10)) + '.' +
FROM emp AS t1
LEFT JOIN emp AS t2
ON ( t1.[UserID] = t2.[UserID]
AND t1.[Day] = t2.[Day]
AND t1.[RowNumber] = ( t2.[RowNumber] - 1 )
AND t2.[RowNumber] % 2 = 0
)
INNER JOIN Employees as E on t1.Userid = E.[ID Number]
GROUP BY t1.[UserID], E.[Last Name]
ORDER BY t1.[UserID]
As mentioned in the comments above there are a LOT of complexities in this type of query. Missed/duplicate punches. Daylight saving time. Holidays, weekends. The types of things that might count as O/T for whatever rules you need. But for a nice clean set of data like you have you can do this fairly easily. This is by no means a complete solution because you have a LOT of things to iron out for details. But this should serve as a decent starting point.
declare #Something table
(
Userid int
, CheckTime datetime
)
insert #Something values
(312, '2018-05-08 05:52:00')
, (312, '2018-05-08 18:06:00')
, (312, '2018-05-10 05:55:00')
, (312, '2018-05-10 18:00:00')
, (312, '2018-05-11 05:58:00')
, (312, '2018-05-11 18:00:00')
, (312, '2018-05-12 05:35:00')
, (312, '2018-05-12 18:00:00');
with OrderedResults as
(
select *
, RowNum = ROW_NUMBER() over(partition by Userid order by CheckTime)
from #Something
)
, InPunch as
(
select *
, GroupNum = Row_Number () over(partition by Userid order by RowNum)
from OrderedResults
where RowNum % 2 = 1
)
, OutPunch as
(
select *
, GroupNum = Row_Number () over(partition by Userid order by RowNum)
from OrderedResults
where RowNum % 2 = 0
)
select ip.Userid
, PunchDate = convert(date, ip.CheckTime)
, CheckIn = ip.CheckTime
, CheckOut = op.CheckTime
from InPunch ip
join OutPunch op on op.GroupNum = ip.GroupNum

How to get all in and out time for an particular employee?

My table is as below:
id time_stamp Access Type
1001 2017-09-05 09:35:00 IN
1002 2017-09-05 11:00:00 IN
1001 2017-09-05 12:00:00 OUT
1002 2017-09-05 12:25:00 OUT
1001 2017-09-05 13:00:00 IN
1002 2017-09-05 14:00:00 IN
1001 2017-09-05 17:00:00 OUT
1002 2017-09-05 18:00:00 OUT
I have tried this query below:
SELECT ROW_NUMBER() OVER (
ORDER BY A.emp_reader_id ASC
) AS SNo
,B.emp_code
,B.emp_name
,CASE
WHEN F.event_entry_name = 'IN'
THEN A.DT
END AS in_time
,CASE
WHEN F.event_entry_name = 'OUT'
THEN A.DT
END AS out_time
,cast(left(CONVERT(TIME, a.DT), 5) AS VARCHAR) AS 'time'
,isnull(B.areaname, 'OAE6080036073000006') AS areaname
,C.dept_name
,b.emp_reader_id
,isnull(c.dept_name, '') AS group_name
,CONVERT(CHAR(11), '2017/12/30', 103) AS StartDate
,CONVERT(CHAR(11), '2018/01/11', 103) AS ToDate
,0 AS emp_card_no
FROM dbo.trnevents AS A
LEFT OUTER JOIN dbo.employee AS B ON A.emp_reader_id = B.emp_reader_id
LEFT OUTER JOIN dbo.departments AS C ON B.dept_id = C.dept_id
LEFT OUTER JOIN dbo.DevicePersonnelarea AS E ON A.POINTID = E.areaid
LEFT OUTER JOIN dbo.Event_entry AS F ON A.EVENTID = F.event_entry_id
ORDER BY A.emp_reader_id ASC
It works but it takes like below. Sometime have same in event and out event :
SNo emp_code emp_name in_time out_time time areaname dept_name emp_reader_id group_name StartDate ToDate emp_card_no
1 102 Ihsan Titi NULL 2017-12-30 12:16:26.000 12:16 Dubai Sales 102 Sales 2017/12/30 2018/01/11 0
2 102 Ihsan Titi NULL 2017-12-30 12:16:27.000 12:16 Dubai Sales 102 Sales 2017/12/30 2018/01/11 0
3 102 Ihsan Titi 2017-12-30 12:44:26.000 NULL 12:44 Dubai Sales 102 Sales 2017/12/30 2018/01/11 0
4 102 Ihsan Titi 2017-12-30 16:27:48.000 NULL 16:27 Dubai Sales 102 Sales 2017/12/30 2018/01/11 0
Expected output:
SNo emp_code emp_name in_time out_time time areaname dept_name emp_reader_id group_name StartDate ToDate emp_card_no
1 102 Ihsan Titi 2017-12-30 12:16:26.000 2017-12-30 12:44:26.000 12:16 Dubai Sales 102 Sales 2017/12/30 2018/01/11 0
2 102 Ihsan Titi 2017-12-30 12:50:26.000 2017-12-30 16:27:48.000 12:16 Dubai Sales 102 Sales 2017/12/30 2018/01/11 0
kindly help i stuck here to get like this..
you can use this :
select A_In.emp_reader_id as empId,A_In.Belongs_to,A_In.DeviceSerialNumber,
DT as EntryTime,
(
select min(DT) as OutTime
from trnevents A_Out
where EVENTID like 'IN'
and A_Out.emp_reader_id = A_In.emp_reader_id
and A_Out.DT > A_In.DT and DATEDIFF(day,A_In.Dt,A_Out.DT)=0
) as ExitTime from trnevents A_In where EVENTID like 'OUT'
from trnevents A_In
The way I've approached it below is to say that if an event is the same type as the event before it then treat it as a "rogue".
Rogues always sit on their own, never paired with any other event.
All other events get paired such that IN is the first item and OUT is the second item.
Then I can group everything up to reduce pairs down to single rows.
WITH
rogue_check
AS
(
SELECT
CASE WHEN LAG(F.event_entry_name) OVER (PARTITION BY A.emp_reader_number ORDER BY A.DT) = F.event_entry_name THEN 1 ELSE 0 END AS is_rogue,
*
FROM
trnevents AS A
LEFT JOIN
EVent_entry AS F
ON F.event_entry_id = A.event_id
),
sorted AS
(
SELECT
ROW_NUMBER() OVER ( ORDER BY DT) AS event_sequence_id,
ROW_NUMBER() OVER (PARTITION BY emp_reader_number, is_rogue ORDER BY DT) AS employee_checked_event_sequence_id,
*
FROM
rogue_check
)
SELECT
MIN(event_sequence_id) AS unique_id,
emp_reader_number,
MAX(CASE WHEN event_entry_name = 'IN' THEN DT END) AS time_in,
MAX(CASE WHEN event_entry_name = 'OUT' THEN DT END) AS time_out
FROM
sorted
GROUP BY
emp_reader_number,
is_rogue,
employee_checked_event_sequence_id - CASE WHEN is_rogue = 1 OR event_entry_name = 'IN' THEN 0 ELSE 1 END
ORDER BY
emp_reader_number,
unique_id
;
Example Schema:
CREATE TABLE trnevents (
emp_reader_number INT,
DT DATETIME,
event_id INT
);
CREATE TABLE Event_entry (
event_entry_id INT,
event_entry_name NVARCHAR(32)
);
Example Data:
INSERT INTO Event_entry VALUES (0, N'IN'), (1, N'OUT');
INSERT INTO trnevents VALUES
(1, '2017-01-01 08:00', 0),
(1, '2017-01-01 08:01', 0),
(1, '2017-01-01 12:00', 1),
(1, '2017-01-01 13:00', 0),
(1, '2017-01-01 17:00', 1),
(1, '2017-01-01 17:01', 1)
;
Example Results:
unique_id emp_reader_number time_in time_out
1 1 01/01/2017 08:00:00 01/01/2017 12:00:00
2 1 01/01/2017 08:01:00 null
4 1 01/01/2017 13:00:00 01/01/2017 17:00:00
6 1 null 01/01/2017 17:01:00
The GROUP BY turned out a bit more fiddly than I anticipated on the train and so may cause an expensive SORT in the execution plan for large data sets. I'll also think about an alternative shortly.
Here is a demo with some simple dummy data demonstrating that it works for those cases at least. (Feel free to update it with other cases if they demonstrate any problems)
http://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d06680d8ed374666760cdc67182aaacb
You can use a PIVOT
select id, [in], out
from
( select
id, time_stamp, accessType,
(ROW_NUMBER() over (partition by id order by time_stamp) -1 )/ 2 rn
from yourtable ) src
pivot
(min(time_stamp) for accessType in ([in],[out])) p
This assumes that each "in" is followed by an "out" and uses row_number to group those pairs of times.

SQL First Time In Last Time Out (Transact-SQL..preferrably)

I have the following table which contains all the time in and time out of people:
CREATE TABLE test (
timecardid INT
, trandate DATE
, employeeid INT
, trantime TIME
, Trantype VARCHAR(1)
, Projcode VARCHAR(3)
)
The task is to get all the earliest trantime with trantype A (perhaps using MIN) and the latest trantime with trantype Z (Using Max), all of which in that trandate (ie. trantype A for july 17 is 8:00 AM and trantype Z for july 17 is 7:00PM).
the problem is, the output should be in the same format as the table where it's coming from, meaning that I have to leave this data and filter out the rest (that aren't the earliest and latest in/out for that date, per employee)
My current solution is to use two different select commands to get all earliest, then get all the latest. then combine them both.
I was wondering though, is there a much simpler, single string solution?
Thank you very much.
EDIT (I apologize, here is the sample. Server is SQL Server 2008):
Timecardid | Trandate | employeeid | trantime | trantype | Projcode
1 2013-04-01 1 8:00:00 A SAMPLE1
2 2013-04-01 1 9:00:00 A SAMPLE1
3 2013-04-01 2 7:00:00 A SAMPLE1
4 2013-04-01 2 6:59:59 A SAMPLE1
5 2013-04-01 1 17:00:00 Z SAMPLE1
6 2013-04-01 1 17:19:00 Z SAMPLE1
7 2013-04-01 2 17:00:00 Z SAMPLE1
8 2013-04-02 1 8:00:00 A SAMPLE1
9 2013-04-02 1 9:00:00 A SAMPLE1
10 2013-04-02 2 7:00:58 A SAMPLE1
11 2013-04-02 2 18:00:00 Z SAMPLE1
12 2013-04-02 2 18:00:01 Z SAMPLE1
13 2013-04-02 1 20:00:00 Z SAMPLE1
Expected Results (the earliest in and the latest out per day, per employee, in a select command):
Timecardid | Trandate | employeeid | trantime | trantype | Projcode
1 2013-04-01 1 8:00:00 A SAMPLE1
4 2013-04-01 2 6:59:59 A SAMPLE1
6 2013-04-01 1 17:19:00 Z SAMPLE1
7 2013-04-01 2 17:00:00 Z SAMPLE1
8 2013-04-02 1 8:00:00 A SAMPLE1
10 2013-04-02 2 7:00:58 A SAMPLE1
12 2013-04-02 2 18:00:01 Z SAMPLE1
13 2013-04-02 1 20:00:00 Z SAMPLE1
Thank you very much
Perhaps this is what you're looking for:
select
t.*
from
test t
where
trantime in (
(select min(trantime) from test t1 where t1.trandate = t.trandate and trantype = 'A'),
(select max(trantime) from test t2 where t2.trandate = t.trandate and trantype = 'Z')
)
Changing my answer to account for the "per employee" requirement:
;WITH EarliestIn AS
(
SELECT trandate, employeeid, min(trantime) AS EarliestTimeIn
FROM test
WHERE trantype = 'A'
GROUP BY trandate, employeeid
),
LatestOut AS
(
SELECT trandate, employeeid, max(trantime) AS LatestTimeOut
FROM test
WHERE trantype = 'Z'
GROUP BY trandate, employeeid
)
SELECT *
FROM test t
WHERE
EXISTS (SELECT * FROM EarliestIn WHERE t.trandate = EarliestIn.trandate AND t.employeeid = EarliestIn.employeeid AND t.trantime = EarliestIn.EarliestTimeIn)
OR EXISTS (SELECT * FROM LatestOut WHERE t.trandate = LatestOut.trandate AND t.employeeid = LatestOut.employeeid AND t.trantime = LatestOut.LatestTimeOut)
Assuming timecardid column is PK or unique, and if I understand it correctly, I would do something like
DECLARE #date DATE
SET #date = '2013-07-01'
SELECT
T0.*
FROM
(SELECT DISTINCT employeeid FROM test) E
CROSS APPLY (
SELECT TOP 1
T.timecardid
FROM
test T
WHERE
T.trandate = #date
AND T.Trantype = 'A'
AND T.employeeid = E.employeeid
ORDER BY T.trantime
UNION ALL
SELECT TOP 1
T.timecardid
FROM
test T
WHERE
T.trandate = #date
AND T.Trantype = 'Z'
AND T.employeeid = E.employeeid
ORDER BY T.trantime DESC
) V
JOIN test T0 ON T0.timecardid = V.timecardid
Appropriate indexes should be set for the table, if you aware of performance.
If you're using SQL server 2012, you can use LAG/LEAD to find the max and min rows in a fairly concise way;
WITH cte AS (
SELECT *,
LAG(timecardid) OVER (PARTITION BY trandate,employeeid,trantype ORDER BY trantime) lagid,
LEAD(timecardid) OVER (PARTITION BY trandate,employeeid,trantype ORDER BY trantime) leadid
FROM test
)
SELECT timecardid,trandate,employeeid,trantime,trantype,projcode
FROM cte
WHERE trantype='A' AND lagid IS NULL
OR trantype='Z' AND leadid IS NULL;
An SQLfiddle to test with.
I would use ROW_NUMBER to sort out the rows you want to select:
;with Ordered as (
select *,
ROW_NUMBER() OVER (PARTITION BY Trandate,employeeid,trantype
ORDER BY trantime ASC) as rnEarly,
ROW_NUMBER() OVER (PARTITION BY Trandate,employeeid,trantype
ORDER BY trantime DESC) as rnLate
from
Test
)
select * from Ordered
where
(rnEarly = 1 and trantype='A') or
(rnLate = 1 and trantype='Z')
order by TimecardId
(SQLFiddle)
It produces the results you've requested, and I think it's quite readable. The reason that trantype is included in the PARTITION BY clauses is so that A and Z values receive separate numbering.

Weird SQL Conditions date , STUCK

I and creating view in SQL Server
Below is my Results , based on this I have to actually achieved the results shown below in the second table
PersonID MonthYear DateTimevalue
---------- ---------- --------------
1 201101 NULL
1 201102 NULL
1 201103 2011-03-10 09:35:57.387
1 201104 2011-04-26 13:25:00.050
1 201105 NULL
1 201106 NULL
1 201107 2011-07-30 16:49:26.050
1 201108 NULL
1 201109 2011-09-21 13:33:42.273
1 201110 2011-10-20 08:55:59.873
1 201111 NULL
1 201112 NULL
So , the case when the DateTimeValue is NULL , we need to take the value from previous month
As shown in the below table
PersonID MonthYear DateTimevalue
---------- ---------- --------------
1 201101 NULL
1 201102 NULL
1 201103 2011-03-10 09:35:57.387
1 201104 2011-04-26 13:25:00.050
1 201105 2011-04-26 13:25:00.050
1 201106 2011-04-26 13:25:00.050
1 201107 2011-07-30 16:49:26.050
1 201108 2011-07-30 16:49:26.050
1 201109 2011-09-21 13:33:42.273
1 201110 2011-10-20 08:55:59.873
1 201111 2011-10-20 08:55:59.873
1 201112 2011-10-20 08:55:59.873
Any help would be great !!!!
Thanks !!!!
Thanks !!!
Try the following:
SELECT A.PersonID,
A.MonthYear,
ISNULL(A.DateTimeValue,B.DateTimeValue) DateTimeValue
FROM YourTable A
OUTER APPLY ( SELECT TOP 1 DateTimeValue
FROM YourTable
WHERE PersonID = A.PersonID
AND MonthYear < A.MonthYear
AND DateTimeValue IS NOT NULL
ORDER BY MonthYear DESC) B
Try this:
select t1.personid
, t1.monthyear
, datetimevalue = prev.datetimevalue
from t1
outer apply
(
select top 1 datetimevalue
, personid
, monthyear
from t1 prev
where prev.monthyear <= t1.monthyear
and prev.datetimevalue is not null
order by monthyear desc
) prev
order by t1.personid
, t1.monthyear
SQLFiddle demo.
Edited to change cross apply to outer apply, #Lamak had it right.
Does Sql Server allow nested SQL in the Select clause?
If it does:
select personId, monthYear,
(select T2.DateTimeValue from Tbl T2
where T2.monthYear=(select max(T3.monthYear) from Tbl T3
where T3.personid=T1.personId and T3.monthYear<=T1.monthYear
and T3.DateTimeValue is not null
)
)
from Tbl T1
i.e.
For each month, find the month less than equal to this month, but
with a not null DateTime;
Look up the DateTime value to the month found in the step above