SQL Server Pivot/Unpivot - sql

I am trying to find a way to convert this table I have that has sorted everything in columns into rows
EmpID Day StartTime
-------------------------------------------------
4 SUNDAY 10:00:00 AM
4 MONDAY 8:00:00 AM
4 TUESDAY 8:00:00 AM
4 WEDNESDAY 8:00:00 AM
4 THURSDAY 8:00:00 AM
4 FRIDAY NULL
4 SATURDAY NULL
800 SUNDAY 10:00:00 AM
800 MONDAY 8:00:00 AM
800 TUESDAY 8:00:00 AM
800 WEDNESDAY 8:00:00 AM
800 THURSDAY 8:00:00 AM
800 FRIDAY NULL
800 SATURDAY NULL
INTO
EmpID Sunday(Start Time) Monday(Start Time)
-------------------------------------------------
4 10:00:00 AM 8:00:00 AM
etc...
I've seen examples of Pivot and Unpivot, but would this be to complex for the pivot/unpivot function?

If you want to use PIVOT
Example
Select *
From YourTable src
Pivot ( max(StartTime) for Day in ([Sunday],[Monday],[Tuesday] ) ) pvt
Note: This assumes YourTable is the 3 columns as displayed in your sample. If you have extra columns, YourTable should be replaced with a sub-query such as
From (Select EmpID,Day,StartTime From YourTable) src

I would just use conditional aggregation:
select empid,
max(case when day = 'Sunday' then StartTime end) as Sunday_StartTime,
max(case when day = 'Monday' then StartTime end) as Monday_StartTime,
. . .
from t
group by empid;

Related

Pivot with Date and time

I have this query for Employee's fingerprints
SELECT
bp.Name
vahruae_date date,
to_char(vahruae_timeofattendance, 'hh:mi') Time
FROM vahruae_hr_empattendancelog empl
INNER JOIN c_bpartner bp ON empl.vahruae_enroll_id = bp.enroll_id
And the value displays as follows
Name Date Time
-----------------------------------------
John 01-SEP-2022 1:00
John 01-SEP-2022 9:00
John 01-SEP-2022 13:00
John 01-SEP-2022 16:00
John 02-SEP-2022 1:00
John 02-SEP-2022 6:00
John 04-SEP-2022 5:00
. .
. .
John 31-SEP-2022 4:30
John 31-SEP-2022 7:00
Ali 01-SEP-2022 10:00
Ali 04-SEP-2022 5:00
. .
. .
I want to display all times in one date row, like this
Name Date Time1 Time2 Time3 Time4
-----------------------------------------------------------------------------------------------
John 01-SEP-2022 1:00 9:00 13:00 16:00
John 02-SEP-2022 1:00 6:00 (null) (null)
John 04-SEP-2022 5:00 (null) (null) (null)
John 31-SEP-2022 4:30 7:00 (null) (null)
Ali 01-SEP-2022 10:00 (null) (null) (null)
Ali 05-SEP-2022 5:00 (null) (null) (null)
I'm not very good with Pivot, but I know I cannot use a subquery inside the Pivot IN Clause.
How can I accomplish that?
Use the ROW_NUMBER analytic function to index the times within a sub-query and then PIVOT using that:
SELECT name,
dt,
time1,
time2,
time3,
time4
FROM (
SELECT empl.vahruae_enroll_id,
bp.Name,
vahruae_date AS dt,
to_char(vahruae_timeofattendance, 'hh:mi') AS Time,
ROW_NUMBER() OVER (
PARTITION BY empl.vahruae_enroll_id,
vahruae_date,
TRUNC(vahruae_timeofattendance)
ORDER BY vahruae_timeofattendance
) AS rn
FROM vahruae_hr_empattendancelog empl
INNER JOIN c_bpartner bp
ON empl.vahruae_enroll_id = bp.enroll_id
)
PIVOT (
MAX(time)
FOR rn IN (1 AS time1, 2 AS time2, 3 AS time3, 4 AS time4)
)
We need to number the hours by date and name using row_number() and then pivoting.
select *
from
(
select "Name" as "Name"
,to_char("Date", 'hh24:mi') as "Time"
,to_date(to_char("Date", 'DD-mon-YYYY')) as "Date"
,row_number() over(partition by "Name", to_char("Date", 'DD-mon-YYYY') order by to_char("Date", 'hh24:mi')) as rn
from t
) t
pivot(max("Time") for rn in('1' as Time1, '2' as Time2, '3' as Time3, '4' as Time4)) p
order by "Name" desc, "Date"
Name
Date
TIME1
TIME2
TIME3
TIME4
John
01-SEP-22
01:00
09:00
13:00
16:00
John
02-SEP-22
01:00
06:00
null
null
John
04-SEP-22
05:00
null
null
null
John
01-OCT-22
04:30
07:00
null
null
Ali
01-SEP-22
10:00
null
null
null
Ali
04-SEP-22
05:00
null
null
null
Fiddle

Query to get unique status from tracking table for a given time

Have scenario to select unique value present in tracking table for a given time
Table values like,
HistoryId EmpId RoleName TimeStamp
1 1 Developer 8:00:00 AM
2 1 Lead 8:00:00 PM
3 1 Mgr 5:00:00 PM
4 2 Lead 8:00:00 AM
5 2 Developer2 5:00:00 PM
6 2 Mgr2 8:00:00 PM
7 3 Mgr 8:00:00 AM
8 3 Lead2 5:00:00 PM
9 3 Developer3 8:00:00 PM
11 3 Developer4 8:30:00 PM
12 1 lead5 8:15:00 PM
Want to select the unique RoleName values for user for a given time,
Example - If I pass time as 6PM, it should select latest status of each user at 6PM
I have tried to select as below, but it is given all the values, where as I need only latest values present for the time value passed
Select EmpId, RoleName, MAX(TimeStamp)
From [dbo].[EmpRoleHistory]
Where TimeStamp <= '2020-02-06 18:00:00.000'
GROUP BY EmpId, RoleName
ORDER BY EmpId
Expected Result:
1 Mgr 2020-02-06 17:00:00.000
2 Developer2 2020-02-06 17:00:00.000
3 Lead2 2020-02-06 17:00:00.000
Current Result:
1 Developer 2020-02-06 08:00:00.000
1 Mgr 2020-02-06 17:00:00.000
2 Developer2 2020-02-06 17:00:00.000
2 Lead 2020-02-06 08:00:00.000
3 Lead2 2020-02-06 17:00:00.000
3 Mgr 2020-02-06 08:00:00.000
The problem is here
GROUP BY EmpId, RoleName
As I can see, the same EmpId have different from RoleName.
So that's the reason why you are getting different values.
demo on db<>fiddle
;with cte_TempTable as
(
Select EmpId, MAX(TimeStamp) as TimeStamp
From [dbo].[EmpRoleHistory]
Where TimeStamp <= '2020-02-06 17:00:00.000'
GROUP BY EmpId
)
select e.*
from EmpRoleHistory e
inner join cte_TempTable c on e.EmpId = c.EmpId
where e.TimeStamp = c.TimeStamp
ORDER BY e.EmpId
Output
Use a subquery and not exists
with cte as
(
Select EmpId, RoleName, TimeStamp
From [dbo].[EmpRoleHistory]
Where TimeStamp <= '2020-02-06 17:00:00.000'
)
select * from cte a
where not exists
(
select 1 from cte b where a.EmpId= b.EmpId and a.TimeStamp < b.TimeStamp
)
Use a subquery or CTE to return the latest EmpRoleHistory record for each employee prior to the supplied datetime, and then join the subquery to the main table to pull out the RoleName value for those records.
Select erh.EmpId, erh.RoleName, erh.TimeStamp
From [dbo].[EmpRoleHistory] as erh
Inner Join (
Select EmpId, MAX(TimeStamp) as TimeStamp
From [dbo].[EmpRoleHistory]
Where TimeStamp <= '2020-02-06 18:00:00.000'
GROUP BY EmpId
) as filt on erh.EmpId = filt.EmpId and
erh.TimeStamp = filt.TimeStamp
Order By erh.EmpId

Need to Return all Available Time periods per Day per Location

I would like to find out how to 'roll up' the following table in Example 1 into Accumulated available time periods, as displayed in Example 2.
The following Code returns all the available Locations and the times that they are free by Day and by Half an hour timeslot.
I need to return the Available time in periods. EG if a location is available from 9am to 5pm that should be displayed.
Code to return Data:
SELECT
LTB.CalendarDate,
LTB.VenueCode,
LTB.VenueName,
LTB.LocationCode,
LTB.LocationDescription,
LTB.OpenTime,
LTB.CloseTime,
LTB.Weekday,
LTB.WeekdayName,
LTB.MaxUniqueParticipants,
LTB.TimeStartBlock,
LTB.TimeEndBlock
FROM
--LTB
--TimeBlock
(
SELECT LocationsOpenClose.CalendarDate,
LocationsOpenClose.StartTime,
LocationsOpenClose.EndTime,
LocationsOpenClose.VenueCode,
LocationsOpenClose.VenueName,
LocationsOpenClose.LocationCode,
LocationsOpenClose.LocationDescription,
LocationsOpenClose.Weekday,
LocationsOpenClose.WeekdayName,
LocationsOpenClose.MaxUniqueParticipants,
LocationsOpenClose.OpenTime,
LocationsOpenClose.CloseTime,
TimeDimension_2.[Time] AS TimeStartBlock,
DATEADD(SECOND, 1799, TimeDimension_2.[Time])
AS TimeEndBlock
--Locations Open Close
FROM (SELECT
dbo.LocationRoster.CalendarDate,
dbo.LocationRoster.StartTime,
dbo.LocationRoster.EndTime,
dbo.Venues.VenueCode,
dbo.Venues.VenueName,
dbo.Locations.LocationCode,
dbo.Locations.Description AS LocationDescription,
DateDimension.Weekday,
DateDimension.WeekdayName,
dbo.Locations.MaxUniqueParticipants,
TimeDimension.[Time] AS OpenTime,
TimeDimension_1.[Time] AS CloseTime
FROM dbo.LocationRoster
INNER JOIN dbo.Locations ON dbo.LocationRoster.LocationGUID = dbo.Locations.LocationGUID
INNER JOIN DateDimension ON dbo.LocationRoster.CalendarDate = DateDimension.[Date]
INNER JOIN TimeDimension ON dbo.LocationRoster.StartTime = TimeDimension.MinuteFromMidnight
INNER JOIN TimeDimension TimeDimension_1 ON dbo.LocationRoster.EndTime = TimeDimension_1.MinuteFromMidnight
INNER JOIN dbo.Venues ON dbo.Locations.VenueGUID = dbo.Venues.VenueGUID) AS LocationsOpenClose
RIGHT OUTER JOIN TimeDimension TimeDimension_2 ON TimeDimension_2.[Time] BETWEEN LocationsOpenClose.OpenTime AND DATEADD(SECOND, -1, LocationsOpenClose.CloseTime)
WHERE (LocationsOpenClose.CalendarDate BETWEEN '2018/08/15' AND '2018/08/16') ) AS LTB
--CETB
--CalendarEntriesStartFinish
LEFT OUTER JOIN
(
SELECT
CalendarEntriesStartFinish.CalendarEntryDate,
CalendarEntriesStartFinish.VenueCode,
CalendarEntriesStartFinish.VenueName,
CalendarEntriesStartFinish.LocationCode,
CalendarEntriesStartFinish.LocationDescription,
CalendarEntriesStartFinish.BookingStartTime,
CalendarEntriesStartFinish.BookingEndTime,
TimeDimension_2.[Time] AS TimeStartBlock,
DATEADD(SECOND, -1, TimeDimension_2.[Time]) AS TimeEndBlock,
CalendarEntriesStartFinish.ReferenceGUID
FROM
(
SELECT
CONVERT(DATE, dbo.CalendarEntries.StartDateTime) AS CalendarEntryDate,
dbo.Venues.VenueCode,
dbo.Venues.VenueName,
dbo.Locations.LocationCode,
dbo.Locations.Description AS LocationDescription,
CONVERT(TIME, dbo.CalendarEntries.StartDateTime) AS BookingStartTime,
DATEADD(SECOND,-1,CONVERT(TIME, dbo.CalendarEntries.FinishDateTime)) AS BookingEndTime,
dbo.CalendarEntries.ReferenceGUID
FROM dbo.CalendarEntries
INNER JOIN dbo.Locations ON dbo.CalendarEntries.LocationGUID = dbo.Locations.LocationGUID
INNER JOIN dbo.Venues ON dbo.Locations.VenueGUID = dbo.Venues.VenueGUID
WHERE (dbo.CalendarEntries.CalendarEntryType IN (0, 2)) AND (dbo.Locations.IsScheduled = 1)
) AS CalendarEntriesStartFinish
RIGHT OUTER JOIN TimeDimension TimeDimension_2 ON TimeDimension_2.[Time] BETWEEN CalendarEntriesStartFinish.BookingStartTime AND CalendarEntriesStartFinish.BookingEndTime
WHERE (CalendarEntriesStartFinish.CalendarEntryDate BETWEEN '2018/08/15' AND '2018/08/16')
) AS CETB ON LTB.CalendarDate = CONVERT(DATE,CETB.CalendarEntryDate) AND LTB.LocationCode = CETB.LocationCode AND LTB.TimeStartBlock = CETB.TimeStartBlock
WHERE (CETB.CalendarEntryDate IS NULL)
ORDER BY LTB.CalendarDate, LTB.LocationDescription, LTB.TimeStartBlock
Example 1: Current Result
CalendarDate VenueCode VenueName LocationCode LocationDescription OpenTime CloseTime Weekday WeekdayName MaxUniqueParticipants TimeStartBlock TimeEndBlock
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 08:30:00 08:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 09:00:00 09:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 11:00:00 11:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 11:30:00 11:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 12:00:00 12:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 12:30:00 12:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 16:00:00 16:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 16:30:00 16:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 17:00:00 17:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 17:30:00 17:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 18:00:00 18:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 18:30:00 18:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 19:00:00 19:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 19:30:00 19:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 20:00:00 20:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 20:30:00 20:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 21:00:00 21:29:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 21:30:00 21:59:59
The above shows all the available times by half an hour. It shows that A Location is not available from 13:00 to 16:00 on 15th August.
Example 2: Desired Result
CalendarDate VenueCode VenueName LocationCode LocationDescription OpenTime CloseTime Weekday WeekdayName MaxUniqueParticipants TimeStartBlock TimeEndBlock
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 08:30:00 12:59:59
15/08/2018 VC Venue Name Lcode A Location 05:30:00 22:00:00 4 Wednesday 20 16:00:00 21:59:59
So that they know A Location is free between 05:30 and 08:30 and then again between 16:00 and 22:00
The Time Dimension table is split in half hour intervals.
A colleague has suggested below, but I cannot get it to work.
WITH t AS (
SELECT LTB.CalendarDate d,ROW_NUMBER() OVER(ORDER BY LTB.CalendarDate) i
FROM #d
GROUP BY LTB.CalendarDate
)
SELECT MIN(d),MAX(d)
FROM t
GROUP BY DATEDIFF(day,i,d)
My SQL is not amazing so full explanations please, your help is appreciated.
here is an example, with my own test data
--build some sample data
select * into #tempt from
(
SELECT cast('20180515' as date) calendardate, 'VC' as VenueCode, CAST('08:30:00' as time) opentime, CAST('08:59:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VC' as VenueCode, CAST('09:00:00' as time) opentime, CAST('09:29:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VC' as VenueCode, CAST('09:30:00' as time) opentime, CAST('15:59:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VC' as VenueCode, CAST('16:30:00' as time) opentime, CAST('18:30:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VX' as VenueCode, CAST('08:30:00' as time) opentime, CAST('08:59:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VX' as VenueCode, CAST('09:00:00' as time) opentime, CAST('09:29:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VX' as VenueCode, CAST('09:45:00' as time) opentime, CAST('15:59:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VX' as VenueCode, CAST('16:45:00' as time) opentime, CAST('18:30:59' as time) closetime union
SELECT cast('20180515' as date) calendardate, 'VX' as VenueCode, CAST('18:31:00' as time) opentime, CAST('22:30:59' as time) closetime
) dq
--now do the work
--build a 'seconds in day' table using the first 3 CTE tables,
;with secs as (select 0 sc union all select sc + 1 from secs where sc < 59),
hrs as (select 0 hr union all select hr + 1 from hrs where hr < 23),
allt as (select dateadd(SECOND, s1.sc + m1.sc * 60 + h1.hr * 3600, CAST('00:00:00' as time)) t from secs s1, secs m1, hrs h1),
island as (select SQ.VenueCode,allt.t, case when exists(select 0 from #tempt tm WHERE SQ.VenueCode = tm.VenueCode and allt.t BETWEEN tm.opentime and tm.closetime) then 1 else 0 end x from allt cross join (SELECT distinct t3.VenueCode FROM #tempt t3) SQ), --calculation starts here
calc as (select VenueCode,t,x,ROW_NUMBER() over(partition by venuecode order by t) - ROW_NUMBER() over(partition by venuecode,x order by t) z from island) --asigns a number to all in an island
select VenueCode,x,MIN(t),MAX(t) from calc group by VenueCode, x,z having x = 1 order by venuecode, MIN(t) --summarises islands
--clear test data
drop table #tempt;

Dense_rank and sum

I have this common table expression
WITH total_hour
AS (
SELECT
employee_id,
SUM(ROUND(CAST(DATEDIFF(MINUTE, start_time, finish_time) AS NUMERIC(18, 0)) / 60, 2)) AS total_h
FROM Timesheet t
WHERE t.employee_id = #employee_id
AND DENSE_RANK() OVER (
ORDER BY DATEDIFF(DAY, '20130925', date_worked) / 7 DESC ) = #rank
GROUP BY t.personnel_id
)
This is the sample data:
ID employee_id worked_date start_time finish_time
1 1 2013-09-25 09:00:00 17:30:00
2 1 2013-09-26 07:00:00 17:00:00
8 1 2013-10-01 09:00:00 17:00:00
9 1 2013-10-04 09:00:00 17:00:00
12 1 2013-10-07 09:00:00 17:00:00
13 1 2013-10-30 09:00:00 17:00:00
14 1 2013-10-28 09:00:00 17:00:00
15 1 2013-11-01 09:00:00 17:00:00
Supposed Wednesday is the first day of the week and my based date is 2013-09-25. I want to get the total number of hours worked from 09-25 to 10-01 when #rank is 1 and total hour from 10-02 to 10-08 when #rank=2 and so on.
Thanks
To get the number of hours worked for an employee within a particular week, just use a suitable WHERE criteria. No need to use DENSE_RANK or similar windowed functions for this.
Assuming you have a #Week parameter, that contains an integer (0 for current week, 1 for last week, 2 for week before that, etc.):
SELECT
employee_id
SUM(ROUND(CAST(DATEDIFF(MINUTE, start_time, finish_time) AS NUMERIC(18, 0)) / 60, 2)) AS total_h
FROM
Timesheet t
WHERE
t.employee_id = #employee_id AND
date_worked BETWEEN DATEADD(ww, DATEDIFF(ww,0,GETDATE()) - #Week, 0)
AND DATEADD(ww, DATEDIFF(ww,0,GETDATE()) - #Week, 0) + 7
Here, I've used the current date (GETDATE()) as the base date, but you could just replace it with 20130925, if that's what you need.

how to query the count of records from first day to last day of the month

I would like to get the count of every day records from my table.
For example I have a table “Employee” with the following fields ID, EmpNo, DateHired.
And I have the following records
ID EmpNo DateHired
1 000001 3/2/2013 12:00:00 AM
2 000002 3/14/2013 12:00:00 AM
3 000003 3/14/2013 12:00:00 AM
4 000004 3/21/2013 12:00:00 AM
5 000005 4/2/2013 12:00:00 AM
6 000006 4/3/2013 12:00:00 AM
7 000007 4/3/2013 12:00:00 AM
8 000008 4/3/2013 12:00:00 AM
9 000009 4/3/2013 12:00:00 AM
10 000010 4/4/2013 12:00:00 AM
11 000011 4/5/2013 12:00:00 AM
12 000012 5/1/2013 12:00:00 AM
And the current month is april,
how can I get this value:
Count Day
0 4/1/2013 12:00:00 AM
1 4/2/2013 12:00:00 AM
4 4/3/2013 12:00:00 AM
1 4/4/2013 12:00:00 AM
1 4/5/2013 12:00:00 AM
0 4/6/2013 12:00:00 AM
0 4/7/2013 12:00:00 AM
0 4/8/2013 12:00:00 AM
Up to
0 4/30/2013 12:00:00 AM
You need to create a calendar for the whole month of April in order to get the whole dates of the month. With the aid of using Common Table Expression, you can get what you want.
After creating a calendar, join it with table Employee using LEFT JOIN so dates will have no matches on table Employee will still be included on the result.
WITH April_Calendar
AS
(
SELECT CAST('20130401' as datetime) AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM April_Calendar
WHERE DATEADD(dd, 1, [date]) <= '20130430'
)
SELECT a.date, COUNT(b.DateHired) totalCount
FROM April_Calendar a
LEFT JOIN Employee b
ON a.date = b.DateHired
GROUP BY a.date
ORDER BY a.date
SQLFiddle Demo
Try
SELECT COUNT(*) as 'Count', DateHired as 'Day'
FROM Employee
WHERE DateHired BEWTEEN %date1 AND %date2
GROUP BY DateHired
Untested, should work though.
This could be the query...
SELECT COUNT(ID) as Count, DateHired FROM Employee GROUP BY DateHired
Following can be helpful in the case...
http://www.sqlite.org/lang_datefunc.html
Hope it helps..