查找两个日期之间的缺失日期

时间:2018-08-26 12:43:52

标签: sql sql-server

我正在尝试使工作人员出勤,我的存储过程正在工作并返回数据。但我看不到休息日。如果没有打孔或打孔,则不会向工作人员显示该天。如何使用以下存储过程返回日期,甚至不进行输入或输出。

DECLARE @startdate DATETIME,
        @enddate DATETIME;

SET @startdate = '2018-07-21';
SET @enddate = '2018-08-21';

WITH calendardates AS 
(
     SELECT date = @startdate

     UNION ALL

     SELECT DATEADD(DAY, 1, date)
     FROM calendardates
     WHERE DATEADD(DAY, 1, date) = @enddate
)
SELECT       
    I.USERID,
    CONVERT(DATETIME, I.WORKDATE) WORKDATE,
    CONVERT(VARCHAR(15), CAST(I.CHECKTIME AS TIME), 100) AS INTIME, 
    CONVERT(VARCHAR(15), CAST(O.CHECKTIME AS TIME), 100) AS OUTTIME,  
    DATEDIFF(n, I.CHECKTIME, O.CHECKTIME) / 60.00 AS WORKHRS,
    DATENAME(dw, (SELECT CONVERT(VARCHAR(10), I.WORKDATE, 101))) AS DutyDay
FROM        
    vwInTime I 
LEFT JOIN 
    vwOutTime O ON I.UserID = O.UserID AND O.WorkDate = I.WorkDate
RIGHT JOIN 
    calendardates c ON I.WorkDate = c.date
WHERE     
    (I.WORKDATE BETWEEN @startdate AND @enddate) 
ORDER BY 
    UserID, WORKDATE 

例如,星期五在这里找不到的样本数据。

84	2018-07-21 00:00:00.000	9:06AM	6:19PM	9.216666	Saturday
84	2018-07-22 00:00:00.000	9:13AM	6:22PM	9.150000	Sunday
84	2018-07-23 00:00:00.000	9:02AM	6:29PM	9.450000	Monday
84	2018-07-24 00:00:00.000	9:06AM	6:29PM	9.383333	Tuesday
84	2018-07-25 00:00:00.000	9:02AM	6:55PM	9.883333	Wednesday
84	2018-07-26 00:00:00.000	9:08AM	6:36PM	9.466666	Thursday
84	2018-07-28 00:00:00.000	1:06PM	NULL	NULL	Saturday
84	2018-07-29 00:00:00.000	1:01PM	10:00PM	8.983333	Sunday
84	2018-07-30 00:00:00.000	1:08PM	10:06PM	8.966666	Monday
84	2018-07-31 00:00:00.000	1:08PM	10:04PM	8.933333	Tuesday
84	2018-08-01 00:00:00.000	1:10PM	10:05PM	8.916666	Wednesday
84	2018-08-02 00:00:00.000	1:12PM	10:07PM	8.916666	Thursday
84	2018-08-04 00:00:00.000	9:07AM	6:25PM	9.300000	Saturday

谢谢。

其他尝试

DECLARE @calendar AS TABLE (
      FullDate DATETIME NOT NULL)
declare  @startdate datetime='2018-07-21',
      @enddate datetime='2018-08-21'
	  WHILE @startdate <= @enddate
BEGIN
INSERT  INTO @calendar
        (FullDate)
	Values(@startdate)
SET @startdate=DATEADD(DAY, 1, @startdate)
END

SELECT       I.USERID,
			 Convert(datetime,I.WORKDATE) WORKDATE,
			 c.FullDate,
			 I.CHECKTIME INTIME, 
			 O.CHECKTIME OUTTIME,
			 Datediff(n,I.CHECKTIME,O.CHECKTIME) / 60.00 AS WORKHRS,
			 datename(dw,(SELECT CONVERT(VARCHAR(10), I.WORKDATE, 101))) As DutyDay
FROM        
             vwInTime I 
			 LEFT JOIN vwOutTime O ON I.UserID = O.UserID AND 
												 O.WorkDate = I.WorkDate
			Right Outer Join @calendar c On I.WorkDate=c.FullDate
												 
WHERE     (I.WORKDATE BETWEEN @startdate AND @enddate) Order By WORKDATE

vwInTime

SELECT     USERID, CONVERT(VARCHAR(10), CHECKTIME, 112) AS WORKDATE, MIN(CHECKTIME) AS CHECKTIME, CHECKTYPE
FROM         dbo.CHECKINOUT
WHERE     (CHECKTYPE = 'I')
GROUP BY USERID, CONVERT(VARCHAR(10), CHECKTIME, 112), CHECKTYPE

nwOutTime

SELECT     USERID, CONVERT(VARCHAR(10), CHECKTIME, 112) AS WORKDATE, MAX(CHECKTIME) AS CHECKTIME, CHECKTYPE
FROM         dbo.CHECKINOUT
WHERE     (CHECKTYPE = 'O')
GROUP BY USERID, CONVERT(VARCHAR(10), CHECKTIME, 112), CHECKTYPE

3 个答案:

答案 0 :(得分:0)

不确定,但是尝试这个。正确的加入只会使我的头部受伤。对于这种模式,请从您的日历表开始,每天一行。然后在日期列上左联接表。

我认为您上面的错误是在WHERE子句中引用外部表,从而使外部联接成为内部联接。

如果您有calandar表,也不需要递归CTE。 EG

DECLARE @startdate DATETIME
,@enddate DATETIME;

SET @startdate = '2018-07-21';
SET @enddate = '2018-08-21';
WITH calendardates
AS ( 
SELECT  date
FROM Calendar
WHERE date between @startdate and  @enddate
)
SELECT       I.USERID,
             Convert(datetime,I.WORKDATE) WORKDATE,
             CONVERT(varchar(15),CAST(I.CHECKTIME AS TIME),100) As INTIME, 
             CONVERT(varchar(15),CAST(O.CHECKTIME AS TIME),100) As OUTTIME,  
             Datediff(n,I.CHECKTIME,O.CHECKTIME) / 60.00 AS WORKHRS,
             datename(dw,(SELECT CONVERT(VARCHAR(10), I.WORKDATE, 101))) As DutyDay
FROM calendardates c
LEFT JOIN vwInTime I 
  ON I.WorkDate = c.date
LEFT JOIN vwOutTime O
  ON I.UserID = O.UserID 
 AND O.WorkDate = I.WorkDate

ORDER BY UserID,WORKDATE 

答案 1 :(得分:0)

我相信使用块创建日历日期的目的是生成从@startdate到@enddate(包括)的日期。如果是这样,您的例行程序不会这样做吗?

DECLARE @startdate DATETIME,
        @enddate DATETIME;

SET @startdate = '20180721';
SET @enddate = '20180821';

WITH calendardates
AS (
SELECT TOP (DATEDIFF(d, @startdate, @enddate) + 1)
           DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY t1.object_id) - 1, @startdate) AS [date]
    FROM sys.all_columns t1
    INNER JOIN sys.all_columns t2
      ON t2.object_id = t1.object_id
)
SELECT       I.USERID,
             coalesce(Convert(datetime,I.WORKDATE), c.Date) as WORKDATE,
             CONVERT(varchar(15),CAST(I.CHECKTIME AS TIME),100) As INTIME, 
             CONVERT(varchar(15),CAST(O.CHECKTIME AS TIME),100) As OUTTIME,  
             Datediff(n,I.CHECKTIME,O.CHECKTIME) / 60.00 AS WORKHRS,
             datename(dw,coalesce(Convert(datetime,I.WORKDATE,101), c.Date)) As DutyDay
FROM        calendardates c 
LEFT JOIN  vwInTime I ON I.WorkDate = c.date 
LEFT JOIN vwOutTime O ON I.UserID = O.UserID AND 
                                                 O.WorkDate = I.WorkDate
Order By UserID,coalesce(Convert(datetime,I.WORKDATE), c.Date);

注意:使用NULL用户ID怎么办?

编辑:这也是处理NULL用户ID的版本:

declare @workTable TABLE
    ([USERID] int, [WORKDATE] datetime, [INTIME] varchar(10), [OUTTIME] varchar(10), [WORKHRS] varchar(8), [DutyDay] varchar(9));

INSERT INTO @workTable
    ([USERID], [WORKDATE], [INTIME], [OUTTIME], [WORKHRS], [DutyDay])
VALUES
    (84, '2018-07-21', '9:06AM', '6:19PM', '9.216666', 'Saturday'),
    (84, '2018-07-22', '9:13AM', '6:22PM', '9.150000', 'Sunday'),
    (84, '2018-07-23', '9:02AM', '6:29PM', '9.450000', 'Monday'),
    (84, '2018-07-24', '9:06AM', '6:29PM', '9.383333', 'Tuesday'),
    (84, '2018-07-25', '9:02AM', '6:55PM', '9.883333', 'Wednesday'),
    (84, '2018-07-26', '9:08AM', '6:36PM', '9.466666', 'Thursday'),
    (84, '2018-07-28', '1:06PM', NULL, NULL, 'Saturday'),
    (84, '2018-07-29', '1:01PM', '10:00PM', '8.983333', 'Sunday'),
    (84, '2018-07-30', '1:08PM', '10:06PM', '8.966666', 'Monday'),
    (84, '2018-07-31', '1:08PM', '10:04PM', '8.933333', 'Tuesday'),
    (84, '2018-08-01', '1:10PM', '10:05PM', '8.916666', 'Wednesday'),
    (84, '2018-08-02', '1:12PM', '10:07PM', '8.916666', 'Thursday'),
    (84, '2018-08-04', '9:07AM', '6:25PM', '9.300000', 'Saturday');

DECLARE @startdate DATETIME,
        @enddate DATETIME;

SET @startdate = '20180721';
SET @enddate = '20180821';

WITH calendardates
AS (
SELECT TOP (DATEDIFF(d, @startdate, @enddate) + 1)
            DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY t1.object_id) - 1, @startdate) AS [date]
    FROM sys.all_columns t1
    INNER JOIN sys.all_columns t2
        ON t2.object_id = t1.object_id
),
userDates AS 
(
  SELECT [date], USERID
  FROM calendardates
  CROSS JOIN (SELECT DISTINCT userid FROM @workTable) t 
)
    SELECT       c.USERID,
                 c.Date as WORKDATE,
                 INTIME, 
                 OUTTIME,  
                 WORKHRS,
                 datename(dw,c.Date) As DutyDay
    FROM        
                userdates c 
                LEFT JOIN @WorkTable wt
    ON wt.WorkDate = c.date AND wt.USERID = c.USERID
    Order By UserID,c.Date;

这是DBFiddle demo.

答案 2 :(得分:0)

问题来了,因为where子句不包含日历日期的条件。添加条件,它应该可以工作

DECLARE @startdate DATETIME,
        @enddate DATETIME;

SET @startdate = '2018-07-21';
SET @enddate = '2018-08-21';

WITH calendardates AS 
(
     SELECT date = @startdate

     UNION ALL

     SELECT DATEADD(DAY, 1, date)
     FROM calendardates
     WHERE DATEADD(DAY, 1, date) = @enddate
)
SELECT       
    I.USERID,
    CONVERT(DATETIME, I.WORKDATE) WORKDATE,
    CONVERT(VARCHAR(15), CAST(I.CHECKTIME AS TIME), 100) AS INTIME, 
    CONVERT(VARCHAR(15), CAST(O.CHECKTIME AS TIME), 100) AS OUTTIME,  
    DATEDIFF(n, I.CHECKTIME, O.CHECKTIME) / 60.00 AS WORKHRS,
    DATENAME(dw, (SELECT CONVERT(VARCHAR(10), I.WORKDATE, 101))) AS DutyDay
FROM        
    vwInTime I 
LEFT JOIN 
    vwOutTime O ON I.UserID = O.UserID AND O.WorkDate = I.WorkDate
RIGHT JOIN 
    calendardates c ON I.WorkDate = c.date
WHERE     
    (I.WORKDATE BETWEEN @startdate AND @enddate) OR c.date is NULL
ORDER BY 
    UserID, WORKDATE 

您可以尝试的另一种方法是使用vwIntime改变条件

DECLARE @startdate DATETIME,
        @enddate DATETIME;

SET @startdate = '2018-07-21';
SET @enddate = '2018-08-21';

WITH calendardates AS 
(
     SELECT date = @startdate

     UNION ALL

     SELECT DATEADD(DAY, 1, date)
     FROM calendardates
     WHERE DATEADD(DAY, 1, date) = @enddate
)
SELECT       
    I.USERID,
    CONVERT(DATETIME, I.WORKDATE) WORKDATE,
    CONVERT(VARCHAR(15), CAST(I.CHECKTIME AS TIME), 100) AS INTIME, 
    CONVERT(VARCHAR(15), CAST(O.CHECKTIME AS TIME), 100) AS OUTTIME,  
    DATEDIFF(n, I.CHECKTIME, O.CHECKTIME) / 60.00 AS WORKHRS,
    DATENAME(dw, (SELECT CONVERT(VARCHAR(10), I.WORKDATE, 101))) AS DutyDay
FROM        
    (SELECT * FROM vwInTime
     WHERE (WORKDATE BETWEEN @startdate AND @enddate)
    ) I 
LEFT JOIN 
    vwOutTime O ON I.UserID = O.UserID AND O.WorkDate = I.WorkDate
RIGHT JOIN 
    calendardates c ON I.WorkDate = c.date
ORDER BY 
    UserID, WORKDATE