我如何在SQL中“自动旋转”附加记录(1变为2,2变为3,3变为4,4变回1)?

时间:2011-09-02 19:18:46

标签: sql sql-server tsql

我正在研究一个用于安排餐厅员工的系统(ASP.NET / MSSQL/C#) 我遇到的问题是我需要每周“自动旋转”班次“InTimes”。

用户需要能够将一天的时间表复制到下周的同一天,所有员工轮班时间都会旋转一个时段。
例如,在下表中,莫妮卡本周一上午10:30,所以她将在下周的上午11:00,亚当将从中午12:00到上午10:30。 轮班之间的时间不是一成不变的,每班轮班的员工人数也不一样 任何有关如何执行此操作的想法(理想情况下使用SQL语句)都将非常感激。

请记住,我是一个相对的新手。

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

9 个答案:

答案 0 :(得分:1)

不知何故,员工需要完成最后(几次)轮班

SELECT TOP 3 * FROM shift WHERE EmployeeID LIKE 'monica' ORDER BY [date] DESC

接下来,他/她需要输入他希望下周工作的时间和日期偏移,相对于之前的时间表。

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

下面: - @recordID是员工选择作为新约会起点的记录 - @dateoffset是添加起始记录的天数 - @timeoffset是要添加到起始记录的分钟数

所有其余部分由用户用作起点的行确定。

答案 1 :(得分:1)

以下是我提出的建议:

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]

答案 2 :(得分:1)

你需要一张包含所有变化的表格:

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) 
);

如果该表已正确填充,则可以运行此表以获取当天Day,Meal,ShiftOrder n-tuple到该日的下一个,$ Mep对的映射:

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;

对于正确填充的表格,每个班次订单必须以1开头,并且在一天中没有跳过或重复的情况下增加一个。

答案 3 :(得分:1)

从@Ben Thul借用大部分#tmp表定义,假设您有一个身份字段,而不是假设您将日期和时间存储为日期和时间......这应该反复运行,复制最新日期进入下一周:

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)

答案 4 :(得分:1)

我假设时间表与下一个答案中的用餐和工作日有关。

另外我想注意,ShiftOrder和Day列不应该是列。日显然由日期确定,因此它是一个完全浪费的空间(计算列或在UI侧确定它)和ShiftOrder由Date和InTime列确定(可能很容易在带有RANK()函数的查询中计算或在UI方面)。这说它会使这个查询更容易:)

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

答案 5 :(得分:1)

这是一个非常简单的面向集合的问题。聚合(count(*)和max())和查找表是不必要的。您可以使用一个SQL语句来完成它。

第一步(设置)是识别那些只是在计划中滑落的员工。

下一步(设定)是将需要“环绕”的员工识别为计划的负责人。

以下是我提出的建议:

/* 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])

答案 6 :(得分:0)

您可以使用子查询(有关子查询的教程,请参阅http://www.databasejournal.com/features/mssql/article.php/3464481/Using-a-Subquery-in-a-T-SQL-Statement.htm)以获取最后一个班次时间。 在此之后,它的琐碎添加和模块划分(如果您不知道那是什么,请查看this)。

希望这有帮助。我现在有点累了,所以我不能给你一个例子。

答案 7 :(得分:0)

我现在是一名SQL程序员和DBA 20年了。话虽如此,这个复杂的业务逻辑应该在系统的C#部分。然后,TDD构建的应用程序可以处理不可避免的变化,并且仍然可以重构和正确。

我的推荐是'推后'。您的回答应该是“这不仅仅是一些查找/填写空白逻辑。这种复杂的业务逻辑属于应用程序”。它属于可以进行单元测试的东西,并且每次更改时都会进行单元测试。

正确的答案有时候是'不',这是其中之一。

答案 8 :(得分:0)

如何为所有员工使用数据透视表,然后将换班时间添加为行?在初始日基于Shift订购名称。

像这样......

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