How to loop over table variable to make specific calculations? - sql
If i have a table variable like that how to loop over this var to make some processing :
DECLARE #userData TABLE(
userId int NOT NULL,
dayDate datetime NOT NULL,
transIn datetime NULL,
transOut datetime NULL,
attIn datetime NULL,
attOut datetime NULL,
MissionIn datetime NOT NULL,
MissionOut datetime NOT NULL,
empState varchar(10) NULL
);
INSERT INTO #userData
SELECT userid, trans_date,transtime_in,transtime_out,att_start_time,att_end_time,#Mission_fromdatetime,#Mission_todatetime,day_flag
FROM datatable_o a
WHERE a.userid = #userid AND a.trans_date = #date ORDER BY transtime_in ;
According to the comments the Whole case :
If the work starts at : att_start_time and ends at att_end_time (work period]
Every employee could check -in and check-out many times in the same date so we could follow him .
the check-in stored in transtime_in
and check-out stored in transtime_out
and i have day_flag so i could know the day is 'W' work day or 'E' weekend
Now considering all these information in addition to the emp_num ,date
I want to calc for the an employee External mission over time :
I have four cases :
No check-in-out && Not work day [weekend] So the employee
should take all the mission period as overtime
No check-in-out && Work day [Absent] so the employee should
take only the mission period out of the work period
There are check-in-outs && Not work day [week end] so the
employee should take only the mission period out of these
check-ins-outs
There are check-ins-outs && work day so the employee should take
only the mission period out of these check-ins-outs and at the same
time out of work period .
Example :
emp_num date att_start att_end mission-in mission-out
672 2015-3-4 07:05:00 13:30:00 12:12:00 20:00:00
emp_num date trans_in trans_out
672 2015-3-4 06:54:00 11:10:00
672 2015-3-4 12:00:00 14:05:00
You can loop through your table by taking help from a copy of that table:
with regard to your question assuming your table is:
DECLARE #userData TABLE(
userId int NOT NULL,
/*Other fields*/
);
and the data of your table is:
INSERT INTO #userData
/*A SELECT or values*/
now create a copy of your table as:
DECLARE #userData_2 TABLE(
userId int NOT NULL,
/*Structure should be the same as #userData*/
);
INSERT INTO #userData_2
SELECT * FROM #userData
now you can do the loop and do whatever you want:
DECLARE #userId INT
WHILE EXISTS (SELECT * FROM #userData_2)
BEGIN
SET #userId=(SELECT TOP 1 userId FROM #userData)
/*
DO YOUR TRANSACTION HERE
*/
DELETE FROM #userData_2 WHERE userId=#userID
END
NOTICE: this assumes the userId is unique, if not then you need to have a unique field, or use a composite fields instead of userId.
Related
How to calculate AVG from two tablets
I am creating a database in SSMS and I am trying to calculate AVG from 2 columns from 2 tablets; I wrote : ;WITH combined AS ( SELECT Datum_narodenia FROM Zamestnanci UNION ALL SELECT Datum_narodenia FROM ObčaniaSR ) SELECT AVG(Datum_narodenia) FROM combined; But I got this error: Msg 8117, Level 16, State 1Operand data type date is invalid for avg operator. I created table 1 : CREATE TABLE Zamestnanci ( id_Zamestnanca int not null, Meno varchar(50) null, Priezvisko varchar(50)null, Adresa varchar(50) null, Datum_narodenia date null, PRIMARY KEY (id_Zamestnanca) ); CREATE TABLE ObčaniaSR ( id_Občana int not null, Meno varchar(50) null, Priezvisko varchar(50) null, Adresa varchar(50) null, Datum_narodenia date null, Zápis_v_trestnom_registry varchar(50) null, PRIMARY KEY (id_Občana) ); Datum_narodenia means Date_of_birth from witch I try to calculate AVG. What is best way to calculate AVG age according to you? Thank you for your answers and advice.
Pretty close calculation of age (lots of nitty-gritty in this answer) uses the difference in hours between the date and now, divided by the number of hours in a year (8766/24 = 365.25), which tries to roughly account for leap years; it is imperfect, but a pretty good trade-off IMHO between simple and right: SELECT DATEDIFF(HOUR,'19860201',GETDATE())/8766; So: ;WITH combined AS ( SELECT Datum_narodenia FROM Zamestnanci UNION ALL SELECT Datum_narodenia FROM ObčaniaSR ) SELECT AVG(DATEDIFF(HOUR, Datum_narodenia, GETDATE())/8766) FROM combined; Now, this does integer math, so you might instead want: ;WITH combined AS ( SELECT Datum_narodenia FROM Zamestnanci UNION ALL SELECT Datum_narodenia FROM ObčaniaSR ) SELECT CONVERT(DECIMAL(5,1), AVG(DATEDIFF(HOUR, Datum_narodenia, GETDATE())/8766.0)) FROM combined;
Provide endDate to previous value
I want to provide an EndDate when the MainAccountNum already exist. The endDate should be applied to the MainAccountNumb with the earliest startDate. So If I have a create table statement like this: Create Table ods.CustomerId( ScreenDate INT NOT NULL, CustomerNum nvarchar(40) NOT NULL, MainAccountNum nvarchar(40) not null, ServiceNum nvarchar(40) not null, StartDate datetime not null, EndDate datetime not null, UpdatedBy nvarchar(50) not null); and say I encounter something in the CustomerNum, MainAccountNum, StartDate, and EndDate like below: 1467823,47382906,2019-08-26 00:00:00.000, Null 1467833,47382906,2019-09-06 00:00:00.000, null When the second record is inserted with that same MainAccountNum the first record should get the startDate of the New Record. The startDate has a default constraint as GetDat() so in the end it should look like: 1467823,47382906,2019-08-26 00:00:00.000,2019-09-06 00:00:00.000 1467833,47382906,2019-09-06 00:00:00.000, null Please Provide code examples of how this can be accomplished
In the stored procedure used to insert new record, have something like begin tran declare #startDate datetime select top 1 #oldStartDate = StartDate from ods.CustomerId where MainAccountNum = #mainAccountNum order by StartDate asc if ##rowcount > 0 update ods.CustomerId set EndDate = #startDate where MainAccountNum = #mainAccountNum and StartDate = #oldStartDate insert ... <your new record here> commit I am assuming that (MainAccountNum, StartDate) tuple is unique and can be used as a key. If not, you have to use whatever is unique for your update statement.
Query Slow down due to structure of WHERE clause
I have a query that creates an #TABLE of a population of interest. It's structure is like this: DECLARE #SepsisTbl TABLE ( PK INT IDENTITY(1, 1) PRIMARY KEY , Name VARCHAR(500) , MRN INT , Account INT , Age INT -- Age at arrival , Arrival DATETIME , Triage_StartDT DATETIME , Left_ED_DT DATETIME , Disposition VARCHAR(500) , Mortality CHAR(1) ); WITH Patients AS ( SELECT UPPER(Patient) AS [Name] , MR# , Account , DATEDIFF(YEAR, AgeDob, Arrival) AS [Age_at_Arrival] , Arrival , Triage_Start , TimeLeftED , Disposition , CASE WHEN Disposition IN ( 'Medical Examiner', 'Morgue' ) THEN 'Y' ELSE 'N' END AS [Mortality] FROM SMSDSS.c_Wellsoft_Rpt_tbl WHERE Triage_Start IS NOT NULL AND ( Diagnosis LIKE '%SEPSIS%' OR Diagnosis LIKE '%SEPTIC%' ) ) INSERT INTO #SepsisTbl SELECT * FROM Patients From this point forward I have 5 more queries of the same sort that are looking for different types of orders that I then LEFT OUTER JOIN onto this table. My question is, why does my performance degrade so much when I change the where clause of the tables from this: AND A.Account IN ( SELECT Account FROM SMSDSS.c_Wellsoft_Rpt_tbl WHERE ( Diagnosis LIKE '%SEPSIS%' OR Diagnosis LIKE '%SEPTIC%' ) to this: AND A.Account IN ( SELECT Account FROM #SepsisTbl ) The run time goes from 2.5 minutes to over 10 minutes with still no results. The CTE itself runs as fast as I can press F5. Thank you,
I suspect that the problem is because the table variable doesn't have an index on Account. If you add an index on Account then I would expect better performance. See the answer to this question for details on how to add an index: Creating an index on a table variable
SQL Server Query Datetime
A table with a date column like create table OrderSold ( ID int primary key identity, SellDate date null, CustID ) Now is there any difference in performance in below two, which one will be recommended most Query #1 select ID from OrderSold where SellDate = '2014-01-31' and Query #2 select ID from OrderSold where SellDate = '01/31/2014' and Query #3 declare #MyDate date = '2014-01-31' select ID from OrderSold where SellDate = #MyDate Thanks..
Let's make a little test. First let's populate the table with 1.000.000 rows. CREATE TABLE OrderSold ( ID INT PRIMARY KEY IDENTITY , SellDate DATE NULL , CustID INT ) GO DECLARE #i INT = 1000000 WHILE #i >= 0 BEGIN INSERT INTO dbo.OrderSold ( SellDate, CustID ) VALUES ( DATEADD(dd, -#i % 1000, GETDATE()), -- SellDate - date #i -- CustID - int ) SET #i = #i - 1 END Now let's see actual execution plans on a heap: As you see execution plans are the same. Now let's create a non clustered covering index on the heap: CREATE INDEX IDX_OrderSold_SellDate ON dbo.OrderSold(SellDate) INCLUDE(ID) Let's see execution plans: As you can see plans are the same again. The only difference is between scan and seek. So the answer to your question is: there is absolutely no difference between those 3 statements. Also notice, as mentioned by #marc_s and #Imran Ali Khan, select ID from OrderSold where SellDate = '01/31/2014' this format is language dependent and may not work on some instances. But as those 3 statement are valid and runnable, they would be the same from performance viewpoint. EDIT: As mentioned by #Martin Smith, this emulation was not completely correct. Let's add additional 4 rows to table: INSERT INTO dbo.OrderSold ( SellDate, CustID ) VALUES ( '20150224', -- SellDate - date 1 -- CustID - int ) INSERT INTO dbo.OrderSold ( SellDate, CustID ) VALUES ( '20150224', -- SellDate - date 2 -- CustID - int ) INSERT INTO dbo.OrderSold ( SellDate, CustID ) VALUES ( '20150224', -- SellDate - date 3 -- CustID - int ) INSERT INTO dbo.OrderSold ( SellDate, CustID ) VALUES ( '20150224', -- SellDate - date 4 -- CustID - int ) SELECT ID FROM OrderSold WHERE SellDate = '2015-02-24' SELECT ID FROM OrderSold WHERE SellDate = '02/24/2015' DECLARE #MyDate DATE = '2015-02-24' SELECT ID FROM OrderSold WHERE SellDate = #MyDate As you can see now there is difference, because estimated rows count was 999 and actual rows count is 4(when estimated rows count of first 2 statements are 4). This is because of parameter sniffing problem. Optimizer doesn't know what is the value of variable is and average density of column from statistics comes into play. Here it is 1000. But you can use query hint OPTION(RECOMPILE) to work around this problem. You can read about it here for example: http://sqlmag.com/sql-server/using-recompile-query-hint-solve-parameter-sniffing-problems
select ID from OrderSold where SellDate = '2014-01-31' is better due to it is ISO8601 format, for more details about date time read this Bad habits to kick : mis-handling date / range queries on above link its describe in details
How could I "auto-rotate" appended records in SQL (1 goes to 2, 2 goes 3, 3 goes to 4, 4 goes back to 1)?
I'm working on a system (ASP.NET/MSSQL/C#) for scheduling restaurant employees. The problem I'm having is I need to "auto-rotate" the shift "InTimes" every week. The user needs to be able to copy one day's schedule to the same day next week with all the employee shift times rotated one slot. For example, in the table below, Monica has the 10:30am shift this Monday, so she would have the 11:00am next week, and Adam would go from 12:00pm to 10:30am. The time between shifts is not constant, nor is the number of employees on each shift. Any ideas on how to do this (ideally with SQL statements) would be greatly appreciated. Please keep in mind I'm a relative novice. RecordID EmpType Date Day Meal ShiftOrder InTime EmployeeID 1 Server 29-Aug-11 Monday Lunch 1 10:30:00 AM Monica 2 Server 29-Aug-11 Monday Lunch 2 11:00:00 AM Sofia 3 Server 29-Aug-11 Monday Lunch 3 11:30:00 AM Jenny 4 Server 29-Aug-11 Monday Lunch 4 12:00:00 PM Adam 5 Server 29-Aug-11 Monday Dinner 1 4:30:00 PM Adam 6 Server 29-Aug-11 Monday Dinner 2 4:45:00 PM Jenny 7 Server 29-Aug-11 Monday Dinner 3 5:00:00 PM Shauna 8 Server 29-Aug-11 Monday Dinner 4 5:15:00 PM Sofia 10 Server 29-Aug-11 Monday Dinner 5 5:30:00 PM Monica
Somehow an employee would need to get his last (few) shifts SELECT TOP 3 * FROM shift WHERE EmployeeID LIKE 'monica' ORDER BY [date] DESC Next he/she would need to enter the time and date offset he would like to work next week, relative to a schedule before. INSERT INTO shift SELECT recordID ,[date] ,CASE [Intime] WHEN [Intime] BETWEEN 00:00 AND 10:00 THEN 'Breakfast' WHEN [Intime] BETWEEN 10:01 AND 04:29 THEN 'Lunch' WHEN [Intime] BETWEEN 04:30 AND 23:59 THEN 'Dinner' END as Meal ,No_idea_how_to_generate_this AS ShiftOrder ,[Intime] ,EmployeeID FROM (SELECT NULL as recordID ,DATEADD(DAY, 7+#dateoffset, ls.[date]) as [date] ,CAST(DATEADD(MINUTE, #timeoffset, ls.[time] AS TIME) as [Intime] ,EmployeeId FROM Shift WHERE recordID = #recordID ) AS subselect Here: - #recordID is the record the employee choose as the starting point for the new appointment. - #dateoffset is the number of days to add the the starting record - #timeoffset is the number of minutes to add to the starting record All the rest is determined by the row the user used as the starting point.
Here's what I came up with: CREATE TABLE #tmp ( [RecordID] INT , [EmpType] VARCHAR(20) , [Date] DATE , [Day] VARCHAR(10) , [Meal] VARCHAR(10) , [ShiftOrder] INT , [InTime] TIME , [EmployeeID] VARCHAR(50) ) INSERT INTO [#tmp] ( [RecordID] , [EmpType] , [Date] , [Day] , [Meal] , [ShiftOrder] , [InTime] , [EmployeeID] ) VALUES (1,'Server','29-Aug-11','Monday','Lunch',1,'10:30:00 AM','Monica'), (2,'Server','29-Aug-11','Monday','Lunch',2,'11:00:00 AM','Sofia'), (3,'Server','29-Aug-11','Monday','Lunch',3,'11:30:00 AM','Jenny'), (4,'Server','29-Aug-11','Monday','Lunch',4,'12:00:00 PM','Adam'), (5,'Server','29-Aug-11','Monday','Dinner',1,'4:30:00 PM','Adam'), (6,'Server','29-Aug-11','Monday','Dinner',2,'4:45:00 PM','Jenny'), (7,'Server','29-Aug-11','Monday','Dinner',3,'5:00:00 PM','Shauna'), (8,'Server','29-Aug-11','Monday','Dinner',4,'5:15:00 PM','Sofia'), (10,'Server','29-Aug-11','Monday','Dinner',5,'5:30:00 PM','Monica'); WITH CountByShift AS (SELECT *, COUNT(1) OVER (PARTITION BY EmpType, [Day], [Meal]) AS [CountByShiftByDayByEmpType] FROM [#tmp] ), NewShiftOrder AS ( SELECT *, ([ShiftOrder] + 1) % [CountByShiftByDayByEmpType] AS [NewShiftOrder] FROM [CountByShift] ) SELECT [RecordID] , [EmpType] , [Date] , [Day] , [Meal] , [ShiftOrder] , CASE WHEN [NewShiftOrder] = 0 THEN [CountByShiftByDayByEmpType] ELSE [NewShiftOrder] END AS [NewShiftOrder], [InTime] , [EmployeeID] FROM NewShiftOrder ORDER BY [RecordID]
You need a table with all of the shifts in it: create table dbo.Shifts ( [Day] varchar(9) not null, Meal varchar(6) not null, ShiftOrder integer not null, InTime time not null, constraint PK__dbo_Shifts primary key ([Day], Meal, ShiftOrder) ); If that table is properly populated you can then run this to get a map of the current Day, Meal, ShiftOrder n-tuple to the next in that Day, Meal pair: with numbers_per_shift as ( select [Day], Meal, max(ShiftOrder) as ShiftOrderCount from dbo.Shifts s group by [Day], Meal ) select s.[Day], s.Meal, s.ShiftOrder, s.ShiftOrder % n.ShiftOrderCount + 1 as NextShiftOrder from dbo.Shifts as s inner join numbers_per_shift as n on s.[Day] = n.[Day] and s.Meal = n.Meal; For the table to be properly populated each of the shift orders would have to begin with one and increase by one with no skipping or repeating within a Day, Meal pair.
Borrowing most of the #tmp table definition from #Ben Thul, assuming you have an identity field, not assuming you are storing dates and times as dates and times...this should run well over and over, copying the latest date into the following week: CREATE TABLE #tmp ( [RecordID] INT , [EmpType] VARCHAR(20) , [Date] VARCHAR(9) , [Day] VARCHAR(10) , [Meal] VARCHAR(10) , [ShiftOrder] INT , [InTime] VARCHAR(11) , [EmployeeID] VARCHAR(50) ) INSERT INTO [#tmp] ( [RecordID] , [EmpType] , [Date] , [Day] , [Meal] , [ShiftOrder] , [InTime] , [EmployeeID] ) VALUES (1,'Server','29-Aug-11','Monday','Lunch',1,'10:30:00 AM','Monica'), (2,'Server','29-Aug-11','Monday','Lunch',2,'11:00:00 AM','Sofia'), (3,'Server','29-Aug-11','Monday','Lunch',3,'11:30:00 AM','Jenny'), (4,'Server','29-Aug-11','Monday','Lunch',4,'12:00:00 PM','Adam'), (5,'Server','29-Aug-11','Monday','Dinner',1,' 4:30:00 PM','Adam'), (6,'Server','29-Aug-11','Monday','Dinner',2,' 4:45:00 PM','Jenny'), (7,'Server','29-Aug-11','Monday','Dinner',3,' 5:00:00 PM','Shauna'), (8,'Server','29-Aug-11','Monday','Dinner',4,' 5:15:00 PM','Sofia'), (10,'Server','29-Aug-11','Monday','Dinner',5,' 5:30:00 PM','Monica'); with Shifts as ( select EmpType, [Day], Meal, ShiftOrder, InTime from #tmp where [Date] = (select max(cast([Date] as datetime)) from #tmp) ), MaxShifts as ( select EmpType, [Day], Meal, max(ShiftOrder) as MaxShiftOrder from #tmp where [Date] = (select max(cast([Date] as datetime)) from #tmp) group by EmpType, [Day], Meal ) insert into #tmp (EmpType, [Date], [Day], Meal, ShiftOrder, InTime, EmployeeID) select s.EmpType , replace(convert(varchar(11), dateadd(dd, 7, cast(a.[Date] as datetime)), 6), ' ', '-') as [Date] , s.Day , s.Meal , s.ShiftOrder , s.InTime , a.EmployeeID from #tmp as a join MaxShifts as m on a.EmpType = m.EmpType and a.[Day] = m.[Day] and a.Meal = m.Meal join Shifts as s on a.EmpType = s.EmpType and a.[Day] = s.[Day] and a.Meal = s.Meal and 1 + a.ShiftOrder % m.MaxShiftOrder = s.ShiftOrder where a.[Date] = (select max(cast([Date] as datetime)) from #tmp)
I'm assuming that the schedule is really tied to a meal and weekday in a below answer. Also I would like to note that ShiftOrder and Day columns should not be columns. Day is obviously determined by Date so it is a total waste of space (computed column OR determine it on the UI side) and ShiftOrder is determined by Date and InTime columns (probably easy to calculate in a query with RANK() function or on the UI side). That said it will make this query a bit easier :) declare #dt date = cast('29-Aug-11' as date) /* note: the date above may be passed from UI or it maybe calculated based on getdate() and dateadd function or s.t. like that */ INSERT INTO [table] (EmpType,Date,Day,Meal,ShiftOrder,InTime,EmployeeID) SELECT t1.EmpType, dateadd(day, 7, t1.date), t1.day, t1.meal, t2.ShiftOrder, t2.InTime, t1.EmployeeID FROM [table] t1 INNER JOIN [table] t2 ON (t1.Date = t2.Date and t1.Meal = t2.Meal and ( t1.ShiftOrder = t2.ShiftOrder + 1 or ( t1.ShiftOrder = (select max(shiftOrder) from [table] where meal = t1.meal and date =t1.date) and t2.ShiftOrder = (select min(shiftOrder) from [table] where meal = t1.meal and date =t1.date) ) ) ) WHERE t1.Date = #dt
This is a pretty straight-forward set-oriented problem. Aggregations (count(*) and max()) and lookup tables are unnecessary. You can do it with one SQL statement. The first step (set) is to identity those employees who simply slide down in the schedule. The next step (set) is to identity those employees who need to "wrap around" to the head of the schedule. Here's what I came up with: /* Set up the temp table for demo purposes */ DROP TABLE #tmp CREATE TABLE #tmp ( [RecordID] INT , [EmpType] VARCHAR(20) , [Date] DATE , [Day] VARCHAR(10) , [Meal] VARCHAR(10) , [ShiftOrder] INT , [InTime] TIME, [EmployeeID] VARCHAR(50) ) INSERT INTO [#tmp] ( [RecordID] , [EmpType] , [Date] , [Day] , [Meal] , [ShiftOrder] , [InTime] , [EmployeeID] ) VALUES (1,'Server','29-Aug-11','Monday','Lunch',1,'10:30:00 AM','Monica'), (2,'Server','29-Aug-11','Monday','Lunch',2,'11:00:00 AM','Sofia'), (3,'Server','29-Aug-11','Monday','Lunch',3,'11:30:00 AM','Jenny'), (4,'Server','29-Aug-11','Monday','Lunch',4,'12:00:00 PM','Adam'), (5,'Server','29-Aug-11','Monday','Dinner',1,' 4:30:00 PM','Adam'), (6,'Server','29-Aug-11','Monday','Dinner',2,' 4:45:00 PM','Jenny'), (7,'Server','29-Aug-11','Monday','Dinner',3,' 5:00:00 PM','Shauna'), (8,'Server','29-Aug-11','Monday','Dinner',4,' 5:15:00 PM','Sofia'), (10,'Server','29-Aug-11','Monday','Dinner',5,' 5:30:00 PM','Monica'); /* the "fills" CTE will find those employees who "wrap around" */ ;WITH fills AS ( SELECT [d2].[EmpType], [d2].[Date], [d2].[Day], [d2].[Meal], 1 AS [ShiftOrder], [d2].[InTime], [d2].[EmployeeID] FROM [#tmp] d1 RIGHT OUTER JOIN [#tmp] d2 ON ([d1].[Meal] = [d2].[Meal]) AND ([d1].[ShiftOrder] = [d2].[ShiftOrder] + 1) WHERE [d1].[EmployeeID] IS NULL ) INSERT INTO [table] (EmpType,Date,Day,Meal,ShiftOrder,InTime,EmployeeID) SELECT [d1].[EmpType], DATEADD(DAY, 7, [d1].[Date]) AS [Date], DATENAME(dw,(DATEADD(DAY, 7, [d1].[Date]))) AS [Day], [d1].[Meal], [d1].[ShiftOrder], [d1].[InTime], ISNULL([d2].[EmployeeID], [f].[EmployeeID]) AS [EmployeeID] FROM [#tmp] d1 LEFT OUTER JOIN [#tmp] d2 ON ([d1].[Meal] = [d2].[Meal]) AND ([d1].[ShiftOrder] = [d2].[ShiftOrder] + 1) LEFT OUTER JOIN [fills] f ON ([d1].[Meal] = [f].[Meal]) AND ([d1].[ShiftOrder] = [f].[ShiftOrder])
You can use a subquery (for a tutorial on subqueries, see http://www.databasejournal.com/features/mssql/article.php/3464481/Using-a-Subquery-in-a-T-SQL-Statement.htm) to get the last shift time. After this, its trivial addition and modular division (in case you don't know what that is, have a look at this). Hope this helped. I'm a bit tired right now, so I can't provide you with an example.
I'm a SQL programmer and DBA for 20 yrs now. With that said, business logic this complex should be in the C# part of the system. Then the TDD built application can handle the inevitable changes, and still be refactor-able and correct. My recommendation is 'push-back'. Your response should be something along the lines of "This isn't just some look-up/fill-in the blank logic. This kind of complex business logic belongs in the App". It belongs in something that can be unit tested, and will be unit tested every time its changed. The right answer sometimes is 'No', this is one of them.
How about using a Pivot Table for all employees and then adding shift timings as rows?? Order the names based on Shift for the initial Day. Something like this.. Date_time Shift_Order Monica Sofia Jenny Adam Shauna 08/29/11 1 10:30AM 11:00AM 11:30AM 12:00PM NULL 08/29/11 2 5:30PM 5:15PM 4:45PM 4:30PM 5:00PM