Calculate Employee Timesheet Data - sql

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

Related

Get all overlapping date ranges when all overlap at the same time

I'm struggling with this for a few days... trying to write an SQL query to get all date ranges when all units overlap at the same time. It's better to see it graphically.
Here is the simplified table with the image for reference:
UnitId Start End
====== ========== ==========
1 05/01/2018 09/01/2018
1 10/01/2018 13/01/2018
2 04/01/2018 15/01/2018
2 19/01/2018 23/01/2018
3 06/01/2018 12/01/2018
3 14/01/2018 22/01/2018
Expected result:
Start End
====== ==========
06/01/2018 09/01/2018
10/01/2018 12/01/2018
What I currently have:
DECLARE #sourceTable TABLE (UnitId int, StartDate datetime, EndDate datetime);
INSERT INTO #sourceTable VALUES
(1, '2018-01-05', '2018-01-09')
,(1, '2018-01-10', '2018-01-13')
,(2, '2018-01-04', '2018-01-15')
,(2, '2018-01-19', '2018-01-23')
,(3, '2018-01-06', '2018-01-12')
,(3, '2018-01-14', '2018-01-22');
SELECT DISTINCT
(SELECT max(v) FROM (values(A.StartDate), (B.StartDate)) as value(v)) StartDate
,(SELECT min(v) FROM (values(A.EndDate), (B.EndDate)) as value(v)) EndDate
FROM #sourceTable A
JOIN #sourceTable B
ON A.startDate <= B.endDate AND A.endDate >= B.startDate AND A.UnitId != B.UnitId
I believe it is "count number of overlapping intervals" problem (this picture should help). Here is one solution to it:
DECLARE #t TABLE (UnitId INT, [Start] DATE, [End] DATE);
INSERT INTO #t VALUES
(1, '2018-01-05', '2018-01-09'),
(1, '2018-01-10', '2018-01-13'),
(2, '2018-01-04', '2018-01-15'),
(2, '2018-01-19', '2018-01-23'),
(3, '2018-01-06', '2018-01-12'),
(3, '2018-01-14', '2018-01-22');
WITH cte1(date, val) AS (
SELECT [Start], 1 FROM #t AS t
UNION ALL
SELECT [End], 0 FROM #t AS t
UNION ALL
SELECT DATEADD(DAY, 1, [End]), -1 FROM #t AS t
), cte2 AS (
SELECT date, SUM(val) OVER (ORDER BY date, val) AS usage
FROM cte1
)
SELECT date, MAX(usage) AS usage
FROM cte2
GROUP BY date
It will give you a list of all dates at which the use count (possibly) changed:
date usage
2018-01-04 1
2018-01-05 2
2018-01-06 3
2018-01-09 3
2018-01-10 3
2018-01-12 3
2018-01-13 2
2018-01-14 2
2018-01-15 2
2018-01-16 1
2018-01-19 2
2018-01-22 2
2018-01-23 1
2018-01-24 0
With this approach you do not need a calendar table or rCTE to build missing dates. Converting the above to ranges (2018-01-05 ... 2018-01-15, 2018-01-19 ... 2018-01-22 etc) is not very difficult.
DECLARE #t TABLE (UnitId INT, [Start] DATE, [End] DATE);
INSERT INTO #t VALUES
(1, '2018-01-05', '2018-01-09'),
(1, '2018-01-10', '2018-01-13'),
(2, '2018-01-04', '2018-01-15'),
(2, '2018-01-19', '2018-01-23'),
(3, '2018-01-06', '2018-01-12'),
(3, '2018-01-14', '2018-01-22');
WITH cte1(date, val) AS (
SELECT [Start], 1 FROM #t AS t -- starting date increments counter
UNION ALL
SELECT [End], 0 FROM #t AS t -- we need all edges in the result
UNION ALL
SELECT DATEADD(DAY, 1, [End]), -1 FROM #t AS t -- end date + 1 decrements counter
), cte2 AS (
SELECT date, SUM(val) OVER (ORDER BY date, val) AS usage -- running sum for counter
FROM cte1
), cte3 AS (
SELECT date, MAX(usage) AS usage -- group multiple events on same date together
FROM cte2
GROUP BY date
), cte4 AS (
SELECT date, usage, CASE
WHEN usage > 1 AND LAG(usage) OVER (ORDER BY date) > 1 THEN 0
WHEN usage < 2 AND LAG(usage) OVER (ORDER BY date) < 2 THEN 0
ELSE 1
END AS chg -- start new group if prev and curr usage are on opposite side of 1
FROM cte3
), cte5 AS (
SELECT date, usage, SUM(chg) OVER (ORDER BY date) AS grp -- number groups for each change
FROM cte4
)
SELECT MIN(date) date1, MAX(date) date2
FROM cte5
GROUP BY grp
HAVING MIN(usage) > 1
Result:
date1 date2
2018-01-05 2018-01-15
2018-01-19 2018-01-22
You are looking for date ranges where all units overlap. So look for start dates where all units exist and end dates where all units exist and then join the two.
I'm using ROW_NUMBER to join the first start date with the first end date, the second start date with the second end date and so on.
select s.startdate, e.enddate
from
(
select startdate, row_number() over (order by startdate) as rn
from #sourceTable s1
where
(
select count(*)
from #sourceTable s2
where s1.startdate between s2.startdate and s2.enddate
) = (select count(distinct unitid) from #sourceTable)
) s
join
(
select enddate, row_number() over (order by startdate) as rn
from #sourceTable s1
where
(
select count(*)
from #sourceTable s2
where s1.enddate between s2.startdate and s2.enddate
) = (select count(distinct unitid) from #sourceTable)
) e on e.rn = s.rn
order by s.startdate;
There may be more elegant ways to solve this, but I guess this query is at least easy to understand :-)
Rextester demo: https://rextester.com/GRRSW89045

Getting a date + 3 days ( using specific date) SQL

I'm trying to get the next available day after a result set.
This is the query I'm using but is totally wrong:
SELECT DateID = ROW_NUMBER() over (order by B.Date_Key) , B.ClosingDate, C.dates AS RecDay
FROM DIM_DATE B JOIN [dbo].[WorkDay_Calendar] C on C.dates = DATEADD(DAY,3, B.ClosingDate) WHERE YEAR(B.ClosingDate) >= '2018'
AND C.[Sentday] = 0 and C.[RecDay] = 0
This query is
retrieving the RecDay when Closingdate +3 days = to Sentday AND What I want is
when Closingdate + 3(Sentday) then pick the next RecDay,
something like C.dates = DATEADD(DAY,3(Sentday), B.ClosingDate).
This is how are looking my tables:
Dim_Date table
WorkDay_Calendar Table
Notice that when Sentday and RecDay are valid when = 0 if 1 is not valid because is a weekend or holiday.
Based on this information for example if I pick from the Dim_Date table 2018-02-02 as one of the Closingdate then the RecDay should be:
DateID RecDay
------------------------
1 2018-02-07
And with the current query is retrieving this which is totally wrong:
DateID RecDay
-----------------------
1 2018-02-05
Graphic explanation below and please follow the 0 in Bold:
More output examples:
Using the dates below as ClosingDate:
Date_Key ClosingDate:
38284 2018-07-24
38287 2018-01-10
38290 2018-03-08
38291 2018-07-13
38293 2018-02-08
Using the same order of the ClosingDates these should be the outputs, I incluided the ClosingDate column so you can follow the order:
OUTPUTS:
DateID ClosingDate RecDay (output)
1 2018-07-24 2018-07-30
2 2018-01-10 2018-01-16
3 2018-03-08 2018-03-13
4 2018-07-13 2018-07-18
5 2018-02-08 2018-02-13
I'm not sure If if followed you correctly, but based on your condition, you want to check the date dimension table based on calendar table. If ClosingDate + 3 days is equal to SentDay then you need to get the ReceiveDay. if that's what you need. then try this out :
UPDATED
SELECT
ROW_NUMBER() OVER (ORDER BY Date_key) DateID,
ClosingDateOLD,
C.Dates
FROM (
SELECT
Date_key,
ClosingDate AS ClosingDateOLD,
CASE
WHEN DATENAME(dw, DATEADD(DAY, 4, ClosingDate)) IN ('Saturday') THEN DATEADD(DAY, 6, ClosingDate)
WHEN DATENAME(dw, DATEADD(DAY, 4, ClosingDate)) IN ('Sunday') THEN DATEADD(DAY, 5, ClosingDate)
ELSE DATEADD(DAY, 5, ClosingDate)
END AS ClosingDate
FROM
#DIM_DATE
WHERE
ClosingDate IS NOT NULL
) D
JOIN #Calendar C ON C.Dates = ClosingDate
As I understand the requirements it would be something like this.
I am posting a full working example in case somebody wants to take a crack at this.
create table #DIM_DATE
(
DateKey int
, ClosingDate date
)
insert #DIM_DATE values
(1, NULL)
, (2, '2018-01-02')
, (3, NULL)
, (4, NULL)
create table #CalendarTable
(
ID int
, SentDay date
, ReceiveDay date
)
insert #CalendarTable values
(1, '2018-01-03', '2018-01-02')
, (2, '2018-01-04', '2018-01-03')
, (3, '2018-01-05', '2018-01-08')
SELECT DateID = ROW_NUMBER() over (order by d.DateKey)
, ct.ReceiveDay
FROM #DIM_DATE d
join #CalendarTable ct on ct.SentDay = dateadd(day, 3, d.ClosingDate)
drop table #DIM_DATE
drop table #CalendarTable

How to duplicate data in sql with conditions

I havea table as table_A . table_A includes these columns
-CountryName
-Min_Date
-Max_Date
-Number
I want to duplicate data with seperating by months. For example
Argentina | 2015-01-04 | 2015-04-07 | 100
England | 2015-02-08 | 2015-03-11 | 90
I want to see a table as this (Monthly seperated)
Argentina | 01-2015 | 27 //(days to end of the min_date's month)
Argentina | 02-2015 | 29 //(days full month)
Argentina | 03-2015 | 31 //(days full month)
Argentina | 04-2015 | 7 //(days from start of the max_date's month)
England | 02-2015 | 21 //(days)
England | 03-2015 | 11 //(days)
I tried too much thing to made this for each records. But now my brain is so confusing and my project is delaying.
Does anybody know how can i solve this. I tried to duplicate each rows with datediff count but it is not working
WITH cte AS (
SELECT CountryName, ISNULL(DATEDIFF(M,Min_Date ,Max_Date )+1,1) as count FROM table_A
UNION ALL
SELECT CountryName, count-1 FROM cte WHERE count>1
)
SELECT CountryName,count FROM cte
-Generate all the dates between min and max dates for each country.
-Then get the month start and month end dates for each country,year,month.
-Finally get the date differences of the month start and month end.
WITH cte AS (
SELECT Country, min_date dt,min_date,max_date FROM t
UNION ALL
SELECT Country, dateadd(dd,1,dt),min_date,max_date FROM cte WHERE dt < max_date
)
,monthends as (
SELECT country,year(dt) yr,month(dt) mth,max(dt) monthend,min(dt) monthstart
FROM cte
GROUP BY country,year(dt),month(dt))
select country
,cast(mth as varchar(2))+'-'+cast(yr as varchar(4)) yr_month
,datediff(dd,monthstart,monthend)+1 days_diff
from monthends
Sample Demo
EDIT: Another option would be to generate all the dates once (the example shown here generates 51 years of dates from 2000 to 2050) and then joining it to the table to get the days by month.
WITH cte AS (
SELECT cast('2000-01-01' as date) dt,cast('2050-12-31' as date) maxdt
UNION ALL
SELECT dateadd(dd,1,dt),maxdt FROM cte WHERE dt < maxdt
)
SELECT country,year(dt) yr,month(dt) mth, datediff(dd,min(dt),max(dt))+1 days_diff
FROM cte c
JOIN t on c.dt BETWEEN t.min_date and t.max_date
GROUP BY country,year(dt),month(dt)
OPTION (MAXRECURSION 0)
I think you have the right idea. But you need to construct the months:
WITH cte AS (
SELECT CountryName, Min_Date as dte, Min_Date, Max_Date
FROM table_A
UNION ALL
SELECT CountryName, DATEADD(month, 1, dte), Min_Date, Max_Date
FROM cte
WHERE dte < Max_date
)
SELECT CountryName, dte
FROM cte;
Getting the number of days in the month is a bit more complicated. That requires some thought.
Oh, I forgot about EOMONTH():
select countryName, dte,
(case when dte = min_date
then datediff(day, min_date, eomonth(dte)) + 1
when dte = max_date
then day(dte)
else day(eomonth(dte))
end) as days
from cte;
Using a Calendar Table makes this stuff pretty easy. RexTester: http://rextester.com/EBTIMG23993
begin
create table #enderaric (
CountryName varchar(16)
, Min_Date date
, Max_Date date
, Number int
)
insert into #enderaric values
('Argentina' ,'2015-01-04' ,'2015-04-07' ,'100')
, ('England' ,'2015-02-08' ,'2015-03-11' ,'90')
end;
-- select * from #enderaric
--*/"
declare #FromDate date;
declare #ThruDate date;
set #FromDate = '2015-01-01';
set #ThruDate = '2015-12-31';
with x as (
select top (cast(sqrt(datediff(day, #FromDate, #ThruDate)) as int) + 1)
[number]
from [master]..spt_values v
)
/* Date Range CTE */
,cal as (
select top (1+datediff(day, #FromDate, #ThruDate))
DateValue = convert(date,dateadd(day,
row_number() over (order by x.number)-1,#FromDate)
)
from x cross join x as y
order by DateValue
)
select
e.CountryName
, YearMonth = convert(char(7),left(convert(varchar(10),DateValue),7))
, [Days]=count(c.DateValue)
from #enderaric as e
inner join cal c on c.DateValue >= e.min_date
and c.DateValue <= e.max_date
group by
e.CountryName
, e.Min_Date
, e.Max_Date
, e.Number
, convert(char(7),left(convert(varchar(10),DateValue),7))
results in:
CountryName YearMonth Days
---------------- --------- -----------
Argentina 2015-01 28
Argentina 2015-02 28
Argentina 2015-03 31
Argentina 2015-04 7
England 2015-02 21
England 2015-03 11
More about calendar tables:
Aaron Bertrand - Generate a set or sequence without loops
generate-a-set-1
generate-a-set-2
generate-a-set-3
David Stein - Creating a Date Table/Dimension on SQL 2008
Michael Valentine Jones - F_TABLE_DATE

Find all rows where start date is before a prior end date

Is there a way to pull back all records that have overlapping datetimes based on a user?
For instance;
TableA has the following rows;
TrainerID StartTime EndTime
1234 10-1-2015 08:30 10-1-2015 09:00
1234 10-1-2015 08:45 10-1-2015 09:15
1234 10-1-2015 09:30 10-1-2015 10:00
2345 10-1-2015 08:45 10-1-2015 09:15
2345 10-1-2015 09:30 10-1-2015 10:00
I need a query that can pull ONLY the following record because it's start time is before the previous end time for the trainer (double booked):
1234 10-1-2015 08:45 10-1-2015 09:15
The EXIST code below should give you that answer. The code ensures that the start time of the clashing entry is before the start of the main list entry while the start time of the clash is still after the start time of the mail list entry.
SELECT *
FROM tblTest clashing
WHERE EXISTS
(
SELECT 1
FROM tblTest mainlist
WHERE clashing.trainderid = mainlist.trainderid
AND clashing.starttime < mainlist.endtime
AND clashing.starttime > mainlist.starttime
)
This can also be written with an IN statement, but EXIST is much more efficient
To remove overlapping dates you can use:
Demo
CREATE TABLE #TABLEA( TrainerID INT, StartDate DATETIME, EndDate DATETIME);
INSERT INTO #TABLEA
SELECT 1234, '10-1-2015 08:30', '10-1-2015 09:00'
UNION ALL SELECT 1234 , '10-1-2015 08:45', '10-1-2015 09:15'
UNION ALL SELECT 1234 , '10-1-2015 09:30', '10-1-2015 10:00'
UNION ALL SELECT 2345 , '10-1-2015 08:45', '10-1-2015 09:15'
UNION ALL SELECT 2345 , '10-1-2015 09:30', '10-1-2015 10:00';
SELECT
D.TrainerID,
[StartTime] = D.StartDate,
[EndTime] = (SELECT MIN(E.EndDate)
FROM #TABLEA E
WHERE E.EndDate >= D.EndDate
AND E.TrainerID = D.TrainerID
AND NOT EXISTS (SELECT 1
FROM #TABLEA E2
WHERE E.StartDate < E2.StartDate
AND E.EndDate > E2.StartDate
AND E.TrainerID = E2.TrainerID))
FROM #TABLEA D
WHERE NOT EXISTS ( SELECT 1
FROM #TABLEA D2
WHERE D.StartDate < D2.EndDate
AND D.EndDate > D2.EndDate
AND D.TrainerID = D2.TrainerID);
You can use below code to get the required row, however based on your logic row from next trainer id (i.e. 2345) will also be qualified
DECLARE #Trainers TABLE
(
TrainerId INT,
Start_Time datetime,
End_Time datetime
)
INSERT INTO #Trainers VALUES
(1234,'10-1-2015 08:30','10-1-2015 09:00 '),
(1234,'10-1-2015 08:45','10-1-2015 09:15'),
(1234,'10-1-2015 09:30','10-1-2015 10:00'),
(2345 ,' 10-1-2015 08:45','10-1-2015 09:15'),
(2345 ,' 10-1-2015 09:30 ',' 10-1-2015 10:00')
;WITH TrainersTemp AS
(
SELECT *, ROW_NUMBER() OVER ( ORDER BY trainerid) AS rn
FROM #Trainers
)
SELECT CX.TrainerId, CX.Start_Time, CX.End_Time
FROM TrainersTemp CX JOIN TrainersTemp CY
ON CX.rn = CY.rn + 1
WHERE CY.End_Time < CX.Start_Time
Demo (SQL fiddle is down again)
or if you want to see all rows except the faulty one then use below code
;WITH TrainersTempAll AS
(
SELECT *, ROW_NUMBER() OVER ( ORDER BY trainerid) AS rn
FROM #Trainers
)
SELECT CX.TrainerId, CX.Start_Time, CX.End_Time
FROM TrainersTempAll CX JOIN TrainersTempAll CY
ON CX.rn = CY.rn + 1
Firstly you should sort by trainerId and Start_time. And then join two tables with correct condition.
Try this query:
;WITH TrainersTemp AS
(
SELECT *, ROW_NUMBER() OVER ( ORDER BY trainerid, Start_Time) AS row_num
FROM Trainers
)
select t2.* from TrainersTemp t1
join TrainersTemp t2 on t1.TrainerId = t2.TrainerId and t1.row_num = t2.row_num-1
where t2.Start_Time<t1.End_Time
As you use SQL Server 2012 you can use LAG function, which would be likely more efficient than self-join. The query becomes pretty simple as well.
For each row LAG gives you EndTime from the previous row (partitioned by TrainerID). Then just compare StartTime from the current row with EndTime from the previous row.
SQL Fiddle
WITH
CTE
AS
(
SELECT
TrainerID
,StartTime
,EndTime
,LAG(EndTime) OVER(PARTITION BY TrainerID ORDER BY StartTime) AS PrevEndTime
FROM TableA
)
SELECT
TrainerID
,StartTime
,EndTime
FROM CTE
WHERE StartTime < PrevEndTime
;
Results:
| TrainerID | StartTime | EndTime |
|-----------|---------------------------|---------------------------|
| 1234 | October, 01 2015 08:45:00 | October, 01 2015 09:15:00 |

Convert Rows into Columns in Sql query

I am having tough time to figure out this please help me
I have time in/out sql query
I have a table looks like below. there are four columns display time in/out info such as...
Date Day Day TimeStamp CheckType
10/11/2014 Sat 8:30 am in
10/11/2014 Sat 11:30am out
10/11/2014 Sat 1:30pm in
10/11/2014 Sat out
10/12/2014 Sun 9:00am in
10/12/2014 Sun 11:20pm out
10/12/2014 Sun 5:20pm out
10/13/2014 Mon 8:00am in
10/13/2014 Mon 6:10pm in
so whoever checkin or checkout then the record will display the result in order and if someone is supposed to check out but accidently pressed in button then this will display as it is (in) or if someone forget to check out then that space will show blank
I am trying to convert rows into column and display such information in below
Date Day Time Type Time Type Time Type Time Type etc-----
10/11/2014 Sat 8:30am in 11:30am out 1:30pm in
10/12/2014 Sun 9:00am in 11:20am out 1:20pm in 6:20pm in
10/13/2014 Mon 8:00am in 6:10pm out
10/14/2014 Tus 8:20am in
etc
I have tried to use pivot
select Date, Day, [1],[2],[3],[4],[5],[6],[7],[8],[9],[10] etc---
from
(
select Date, Day, Stamptime, CheckTime, userID
from a table
)
pivot
(
max(StampTime)
for stamptime in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10] etc---)
) as PivotTable
can anyone explain how to convert the rows into columns I have spent many days already.
Here's something close to what you're after, making use of XML to get the variable number of columns. As mentioned in my comment above though, I don't recommend this approach.
SQL Fiddle: http://sqlfiddle.com/#!3/e5b325/2
select [Date]
, [Day]
, (
select [TimeStamp] [Time]
, [CheckType] [Type]
from aTable b
where b.[Date] = a.[Date]
order by [TimeStamp], [CheckType]
for xml path ('')
) CheckInAndOutInfo
from aTable a
group by [Date], [Day]
order by [Date]
Output:
DATE DAY CHECKINANDOUTINFO
2014-10-11 Sat <Type>Out</Type><Time>08:30:00</Time><Type>In</Type><Time>11:30:00</Time><Type>Out</Type><Time>13:30:00</Time><Type>In</Type>
2014-10-12 Sun <Time>09:00:00</Time><Type>In</Type><Time>17:20:00</Time><Type>Out</Type><Time>23:20:00</Time><Type>Out</Type>
2014-10-13 Mon <Time>08:00:00</Time><Type>In</Type><Time>18:10:00</Time><Type>In</Type>
Alternatively, if you can guarantee you'll never have more than a certain number of check-ins/outs per day, you could do the following (this assumes no more than 5 per day):
SQL Fiddle: http://sqlfiddle.com/#!3/e5b325/4
select *
from
(
select [Date], [Day]
, 'T' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([TimeStamp] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
union all
select [Date], [Day]
, 'C' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([CheckType] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
) x
pivot
(
min(pvtVal)
for r in ([T1], [C1], [T2], [C2], [T3], [C3], [T4], [C4], [T5], [C5])
) y
order by [Date]
Output:
DATE DAY T1 C1 T2 C2 T3 C3 T4 C4 T5 C5
2014-10-11 Sat 08:30:00.0000000 In 11:30:00.0000000 Out 13:30:00.0000000 In (null) (null) (null) (null)
2014-10-12 Sun 09:00:00.0000000 In 17:20:00.0000000 Out 23:20:00.0000000 Out (null) (null) (null) (null)
2014-10-13 Mon 08:00:00.0000000 In 18:10:00.0000000 In (null) (null) (null) (null) (null) (null)
...Or if you wanted to use dynamic SQL, you could do this:
SQL Fiddle: http://sqlfiddle.com/#!3/e5b325/6
declare #sql nvarchar(max)
select #sql = coalesce(#sql+',','') + QUOTENAME('T' + CAST(x as nvarchar)) + ',' + QUOTENAME('C' + CAST(x as nvarchar))
from
(
select distinct row_number() over (partition by [Date] order by [Date]) x
from aTable
where [TimeStamp] is not null
) y
order by x
set #sql =
'select *
from
(
select [Date], [Day]
, ''T'' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([TimeStamp] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
union all
select [Date], [Day]
, ''C'' + CAST(ROW_NUMBER() over (partition by [Date] order by [TimeStamp], [CheckType]) as nvarchar) r
, cast([CheckType] as nvarchar) pvtVal
from aTable
where [TimeStamp] is not null
) x
pivot
(
min(pvtVal)
for r in (' + #sql + ')
) y
order by [Date]'
exec (#sql)
Output:
DATE DAY T1 C1 T2 C2 T3 C3
2014-10-11 Sat 08:30:00.0000000 In 11:30:00.0000000 Out 13:30:00.0000000 In
2014-10-12 Sun 09:00:00.0000000 In 17:20:00.0000000 Out 23:20:00.0000000 Out
2014-10-13 Mon 08:00:00.0000000 In 18:10:00.0000000 In (null) (null)