Sql building的入口和出口记录

时间:2018-05-26 16:05:50

标签: mysql sql mysql-5.5

从下表中:

timestamp         inout  Name
2018-04-01 14:00    0    Tom
2018-04-02 06:00    1    Tom
2018-04-02 14:00    0    Tom
2018-04-03 06:00    1    Tom
2018-04-01 22:00    0    Rob
2018-04-02 14:00    1    Rob
2018-04-02 22:00    0    Rob
2018-04-03 13:00    1    Rob
2018-04-01 12:55    0    John
2018-04-02 06:05    1    John
2018-04-03 06:10    1    John
2018-04-01 14:05    0    Anna
2018-04-02 14:10    0    Anna
2018-04-02 14:15    1    Anna
2018-04-02 14:20    0    Anna
2018-04-03 14:05    0    Anna
2018-04-01 22:00    1    Mary
2018-04-02 06:00    0    Mary
2018-04-02 22:00    1    Mary
2018-04-03 06:00    0    Mary

其中1 = in 0 = out
我需要收集2018-04-02的“进入和退出记录”的数据,并将其显示在这样的表中:

d1-in-timestamp   d0-out-timestamp  Name
2018-04-02 07:00  2018-04-02 15:00  Tom
2018-04-02 14:00  2018-04-02 22:00  Rob
2018-04-02 06:05  -                 John
-                 2018-04-02 14:10  Anna
2018-04-02 14:15  2018-04-02 14:20  Anna
2018-04-02 00:00  2018-04-02 06:00  Mary
2018-04-02 22:00  2018-04-02 00:00  Mary

在一个完美的世界中
汤姆通过“DOOR”进入建筑物,然后经过“DOOR”一次。汤姆很完美!就像汤姆! :)
罗布也很完美,但他很瞌睡,所以他来到了下午班。 :P
安娜来和汤姆一起工作。汤姆为她保持“DOOR”开放,所以没有关于她进入的记录。此外,她一直回来,因为她忘记了什么 约翰是道奇!他上班迟到了,所以他应该解决这个问题,但是当别人离开时,他总是和别人一起通过“DOOR”滑倒。
最后结婚。她是夜班,所以她需要在一张桌子上看到一天分成两条记录
是否可以通过一个SQL查询在一个表中获得此类结果?

到目前为止,我管理这样的SQL查询:

select timestamp as d1, (select timestamp from DOOR where timestamp>m1.timestamp and inout=0 and name=m1.name) as d0, name from DOOR as m1 where substring(timestamp,1,10)='2018-04-02' and inout=1 order by name, timestamp 

查询适用于来自“完美世界”(Tom& Rob)的人,而对于约翰则更多/更少 不幸的查询对Anna和Marry不起作用。

PS:抱歉我的英文

4 个答案:

答案 0 :(得分:1)

这是一个艰难的,但我想出了一个查询来做到这一点。它使用一些连接,子查询和联合,但会产生您想要的输出。我从@RajatJaiswals小提琴开始,但创建了一个全新的查询。

SELECT * FROM (
  SELECT
    IF(inA.timestamp < '2018-04-02', '2018-04-02 00:00:00', inA.timestamp) AS `d1-in-timestamp`, 
    IFNULL(IF(outA.timestamp > '2018-04-02 23:59:59', CAST(DATE_ADD('2018-04-02', INTERVAL 1 DAY) AS DATETIME), outA.timestamp), '-') AS `d0-out-timestamp`, 
    inA.name AS `Name`
  FROM 
    attendance AS inA
    LEFT JOIN attendance AS outA ON (
      inA.name = outA.name 
      AND outA.inout = 0
      AND inA.timestamp < outA.timestamp
      AND NOT EXISTS(
        SELECT betweenA.name 
        FROM attendance AS betweenA
        WHERE 
          betweenA.name = inA.name
          AND betweenA.timestamp > inA.timestamp
          AND betweenA.timestamp < outA.timestamp
      )
    )
  WHERE 
    inA.inout = 1 
    AND (
      CAST(inA.timestamp AS DATE) = '2018-04-02' 
      OR CAST(outA.timestamp AS DATE) = '2018-04-02'
    )

  UNION

  SELECT 
    '-' AS `d1-in-timestamp`, 
    IF(outA.timestamp > '2018-04-02 23:59:59', CAST(DATE_ADD('2018-04-02', INTERVAL 1 DAY) AS DATETIME), outA.timestamp) AS `d0-out-timestamp`, 
    outA.name AS `Name`
  FROM
    attendance AS outA
    LEFT JOIN attendance AS inA ON (
      inA.name = outA.name 
      AND inA.inout = 1
      AND inA.timestamp < outA.timestamp
      AND NOT EXISTS(
        SELECT betweenA.name 
        FROM attendance AS betweenA
        WHERE 
          betweenA.name = inA.name
          AND betweenA.timestamp > inA.timestamp
          AND betweenA.timestamp < outA.timestamp
      )
    )
  WHERE 
    outA.inout = 0
    AND  CAST(outA.timestamp AS DATE) = '2018-04-02'
    AND inA.name IS NULL
) AS a
ORDER BY `Name`, `d1-in-timestamp`

这是一个复杂的查询,起初可能看起来令人生畏,但我尝试将其分解成一小部分来解释它的作用:

外部SELECT只是为了对整个结果进行排序。由于UNION语句,这是必需的。

  • 第一个内部SELECT子句只处理一些输出转换
    • IF(inA.timestamp < '2018-04-02', '2018-04-02 00:00:00', inA.timestamp)仅用于格式化,并替换当天00:00之前的时间戳,
    • IFNULL(IF(outA.timestamp > '2018-04-02 23:59:59', CAST(DATE_ADD('2018-04-02', INTERVAL 1 DAY) AS DATETIME), outA.timestamp), '-')再次进行格式化,但会做两件事:如果此人没有离开建筑物,则将{null替换为-,并替换当天的00:00之后的时间戳。
    • 之后的第二天
  • FROM子句中,我使用JOIN将进入建筑物(inA)的记录与离开建筑物的记录(outA)连接在一起。有趣的部分是ON子句:

    • 我使用name列仅加入同一个人的记录
    • outA表应该只关注离开的人(inout = 0
    • inA.timestamp < outA.timestamp如果此人在离开这两个条目之前没有进入,则不应该联合起来
    • 加入的两个记录之间不应有任何活动记录。这由NOT EXISTS子查询处理。它搜索

      的任何记录
      • 属于同一个人(betweenA.name = inA.name
      • 发生在inA有问题的记录
      • 之后
      • 发生在有问题的outA记录之前

      如果存在任何此类记录,则NOT EXISTS子句的计算结果为false,并且记录未加入。这样,只有后续的进出条目才能连接在一起。

  • WHERE子句很简单:
    • 确保仅从inA
    • 中选择进入建筑物的人员
    • 至少有一个条目必须来自所需的日期(CAST(inA.timestamp AS DATE)将时间戳转换为日期,从而删除时间部分并简化比较)

选择记录进入建筑物的人的所有记录。我们现在仍然怀念没有记录进入大楼的安娜的情况。这就是UNION到位的地方,并将此信息添加到结果中。

  • SELECT再次只是输出逻辑:
    • 我们没有人进入的记录,因此永远不会有时间戳。只需返回&#39; - &#39;入境时间
    • 处理离开时间戳的逻辑与上面的相同
  • 这次我们从离开记录开始,并将输入的记录加入到他们中。 ON子句执行以下操作:
    • 使用name仅加入一个人的记录
    • outA.inout = 1因为联接表应该只使用输入
    • 的记录
    • 进入的记录应在建筑物离开前进行(inA.timestamp < outA.timestamp
    • 如上所述,
    • 之间可能没有任何其他记录
  • WHERE子句再次执行一些重要限制:
    • outA.inout = 0因为我们需要将表限制在离开建筑物的记录中
    • CAST(outA.timestamp AS DATE) = '2018-04-02'这次只检查outA的日期,因为没有进入记录。
    • 仅在没有输入记录的情况下使用结果(即没有找到记录的地方)。如果是inA.name IS NULL
    • ,就属于这种情况

最后一件事是ORDER BY条款应该是自我解释的。

输出:

|     d1-in-timestamp |    d0-out-timestamp | Name |
|---------------------|---------------------|------|
|                   - | 2018-04-02 14:10:00 | Anna |
| 2018-04-02 14:15:00 | 2018-04-02 14:20:00 | Anna |
| 2018-04-02 06:05:00 |                   - | John |
| 2018-04-02 00:00:00 | 2018-04-02 06:00:00 | Mary |
| 2018-04-02 22:00:00 | 2018-04-03 00:00:00 | Mary |
| 2018-04-02 14:00:00 | 2018-04-02 22:00:00 |  Rob |
| 2018-04-02 06:00:00 | 2018-04-02 14:00:00 |  Tom |

您可以在以下SQL小提琴中尝试:http://sqlfiddle.com/#!9/e618bb/7/0

答案 1 :(得分:0)

SELECT t.Name,t1.TimeStamp asintime,t.timestamp as outtime
FROM tmpAttendance t
LEFT OUTER JOIN tmpAttendance t1 ON t.Name = t1.Name 
                 AND t1.inout = 1
                 AND CAST(t.timestamp AS DATE) =  CAST(t1.timestamp AS DATE)
 WHERE t.inout = 0
 ORDER BY t.Name ,t1.TimeStamp

http://sqlfiddle.com/#!9/df678/5

答案 2 :(得分:0)

只需添加一个简单的内容:为Mary添加一行最长时间(2018-04-02 23:59) http://sqlfiddle.com/#!9/bb41b1/6
然后你可以使用下面的逻辑:

  select a.name,
    case when a.name != 'Mary' and (b.out_time is null or a.in_time <= b.out_time) then a.in_time
         when a.name != 'Mary' and b.out_time is not null and a.in_time > b.out_time then null
         when a.name = 'Mary' and  a.in_time > b.out_time then '2018-04-02 00:00:00'
         else a.in_time
    end as in_time,
    b.out_time     
    from
    (select name,time1 as in_time  from have1
    where inout1 = 1 and (substring(time1,1,10)='2018-04-02')) a
    left join
    (select name,time1 as out_time  from have1
    where inout1 = 0 and (substring(time1,1,10)='2018-04-02')) b
    on a.name = b.name   

<强>输出:

name    in_time                 out_time
Tom     2018-04-02 06:00:00  2018-04-02 14:00:00
Rob     2018-04-02 14:00:00  2018-04-02 22:00:00
John    2018-04-02 06:05:00  (null)
Anna    (null)               2018-04-02 14:10:00
Anna    2018-04-02 14:15:00  2018-04-02 14:20:00
Mary    2018-04-02 00:00:00  2018-04-02 06:00:00
Mary    2018-04-02 22:00:00  2018-04-02 23:59:59

如果需要澄清,请告诉我。

答案 3 :(得分:0)

玛丽和其他夜班人员

首先让我们谈谈玛丽和其他夜班人员。当我们过滤所有邮票时,我们可以找到它们的邮票:

  • 同一天没有印章,即印章之后。
  • 但是第二天出现了一张标记,第二天没有标记在那张标记之前。

对于外面的印章,我们使用印章的第二天00:00。

SELECT d.`timestamp` `timestamp_in`,
       timestampadd(second, -1 * second(d.`timestamp`), timestampadd(minute, -1 * minute(d.`timestamp`), timestampadd(hour, -1 * hour(d.`timestamp`), timestampadd(day, 1, (d.`timestamp`))))) `timestamp_out`,
       d.`name`
       FROM `door` d
       WHERE d.`inout` = 1
             AND NOT EXISTS (SELECT *
                                    FROM `door` di
                                    WHERE di.`name` = d.`name`
                                          AND di.`inout` = 0
                                          AND di.`timestamp` > d.`timestamp`
                                          AND date(di.`timestamp`) = date(d.`timestamp`))
             AND EXISTS (SELECT *
                                FROM `door` di
                                WHERE di.`name` = d.`name`
                                      AND di.`inout` = 0
                                      AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                      AND NOT EXISTS (SELECT *
                                                             FROM `door` dii
                                                             WHERE dii.`name` = di.`name`
                                                                   AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                   AND dii.`timestamp` < di.`timestamp`
                                                                   AND dii.`inout` = 1))

为了获得夜间移位器的标记,我们可以使用相同的逻辑,但切换所有内容:

  • >在日期比较中变为<,反之亦然。
  • 如果标记为0,则标记为1,反之亦然。
  • 而不是第二天,与前一天相比。

对于印章,我们使用印章日当天00:00。

如果我们UNION ALL两个查询,我们都会获得夜间移位器的部分。

该团伙的其余成员也称为日间移民

现在可以轻松获得日间移位器的印章。我们只需要否定我们用来寻找夜班者的条件。获得印章:

SELECT d.`timestamp`,
       d.`name`
       FROM `door` d
       WHERE d.`inout` = 1
             AND (EXISTS (SELECT *
                                 FROM `door` di
                                 WHERE di.`name` = d.`name`
                                       AND di.`inout` = 0
                                       AND di.`timestamp` > d.`timestamp`
                                       AND date(di.`timestamp`) = date(d.`timestamp`))
                   OR NOT EXISTS (SELECT *
                                         FROM `door` di
                                         WHERE di.`name` = d.`name`
                                               AND di.`inout` = 0
                                               AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                               AND NOT EXISTS (SELECT *
                                                                      FROM `door` dii
                                                                      WHERE dii.`name` = di.`name`
                                                                            AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                            AND dii.`timestamp` < di.`timestamp`
                                                                            AND dii.`inout` = 1)))

对于外面的邮票,我们可以再次切换,>变为<等,如上所述。

当然,在这里我们不能为邮票生成印章,反之亦然。我们必须FULL OUTER JOIN邮票和日间变速器的邮票。但MySQL至少在较低版本中不支持此操作。因此,我们按UNION两个LEFT JOIN进行操作,将第二个LEFT JOIN中的角色与第一个角色进行比较。

全部放在一起

现在我们可以简单地UNION ALL夜间移民和日间移位器来获取所有日子的完整结果。我们可以SELECT FROM,以获得特定日期的结果:

SELECT coalesce(x.`timestamp_in`, '-') `d1-in-timestamp`,
       coalesce(x.`timestamp_out`, '-') `d0-out-timestamp`,
       x.`name`
       FROM (SELECT r.`timestamp` `timestamp_in`,
                    s.`timestamp` `timestamp_out`,
                    r.`name`
                    FROM (SELECT d.`timestamp`,
                                 d.`name`
                                 FROM `door` d
                                 WHERE d.`inout` = 1
                                       AND (EXISTS (SELECT *
                                                           FROM `door` di
                                                           WHERE di.`name` = d.`name`
                                                                 AND di.`inout` = 0
                                                                 AND di.`timestamp` > d.`timestamp`
                                                                 AND date(di.`timestamp`) = date(d.`timestamp`))
                                             OR NOT EXISTS (SELECT *
                                                                   FROM `door` di
                                                                   WHERE di.`name` = d.`name`
                                                                         AND di.`inout` = 0
                                                                         AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                         AND NOT EXISTS (SELECT *
                                                                                                FROM `door` dii
                                                                                                WHERE dii.`name` = di.`name`
                                                                                                      AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                                      AND dii.`timestamp` < di.`timestamp`
                                                                                                      AND dii.`inout` = 1)))) r
                         LEFT JOIN (SELECT d.`timestamp`,
                                           d.`name`
                                           FROM `door` d
                                           WHERE d.`inout` = 0
                                                 AND (EXISTS (SELECT *
                                                                     FROM `door` di
                                                                     WHERE di.`name` = d.`name`
                                                                           AND di.`inout` = 1
                                                                           AND di.`timestamp` < d.`timestamp`
                                                                           AND date(di.`timestamp`) = date(d.`timestamp`))
                                                       OR NOT EXISTS (SELECT *
                                                                             FROM `door` di
                                                                             WHERE di.`name` = d.`name`
                                                                                   AND di.`inout` = 1
                                                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                   AND NOT EXISTS (SELECT *
                                                                                                          FROM `door` dii
                                                                                                          WHERE dii.`name` = di.`name`
                                                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                                                AND dii.`timestamp` > di.`timestamp`
                                                                                                                AND dii.`inout` = 0)))) s
                                   ON s.`name` = r.`name`
                                      AND date(s.`timestamp`) = date(r.`timestamp`)
                                      AND s.`timestamp` > r.`timestamp`
             UNION
             SELECT u.`timestamp` `timestamp_in`,
                    t.`timestamp` `timestamp_out`,
                    t.`name`
                    FROM (SELECT d.`timestamp`,
                                 d.`name`
                                 FROM `door` d
                                 WHERE d.`inout` = 0
                                       AND (EXISTS (SELECT *
                                                           FROM `door` di
                                                           WHERE di.`name` = d.`name`
                                                                 AND di.`inout` = 1
                                                                 AND di.`timestamp` < d.`timestamp`
                                                                 AND date(di.`timestamp`) = date(d.`timestamp`))
                                             OR NOT EXISTS (SELECT *
                                                                   FROM `door` di
                                                                   WHERE di.`name` = d.`name`
                                                                         AND di.`inout` = 1
                                                                         AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                         AND NOT EXISTS (SELECT *
                                                                                                FROM `door` dii
                                                                                                WHERE dii.`name` = di.`name`
                                                                                                      AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                                      AND dii.`timestamp` > di.`timestamp`
                                                                                                      AND dii.`inout` = 0)))) t
                         LEFT JOIN (SELECT d.`timestamp`,
                                           d.`name`
                                           FROM `door` d
                                           WHERE d.`inout` = 1
                                                 AND (EXISTS (SELECT *
                                                                     FROM `door` di
                                                                     WHERE di.`name` = d.`name`
                                                                           AND di.`inout` = 0
                                                                           AND di.`timestamp` > d.`timestamp`
                                                                           AND date(di.`timestamp`) = date(d.`timestamp`))
                                                       OR NOT EXISTS (SELECT *
                                                                             FROM `door` di
                                                                             WHERE di.`name` = d.`name`
                                                                                   AND di.`inout` = 0
                                                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                   AND NOT EXISTS (SELECT *
                                                                                                          FROM `door` dii
                                                                                                          WHERE dii.`name` = di.`name`
                                                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                                                AND dii.`timestamp` < di.`timestamp`
                                                                                                                AND dii.`inout` = 1)))) u
                                   ON u.`name` = t.`name`
                                      AND date(u.`timestamp`) = date(t.`timestamp`)
                                      AND u.`timestamp` < t.`timestamp`
             UNION
             SELECT d.`timestamp` `timestamp_in`,
                    timestampadd(second, -1 * second(d.`timestamp`), timestampadd(minute, -1 * minute(d.`timestamp`), timestampadd(hour, -1 * hour(d.`timestamp`), timestampadd(day, 1, (d.`timestamp`))))) `timestamp_out`,
                    d.`name`
                    FROM `door` d
                    WHERE d.`inout` = 1
                          AND NOT EXISTS (SELECT *
                                                 FROM `door` di
                                                 WHERE di.`name` = d.`name`
                                                       AND di.`inout` = 0
                                                       AND di.`timestamp` > d.`timestamp`
                                                       AND date(di.`timestamp`) = date(d.`timestamp`))
                          AND EXISTS (SELECT *
                                             FROM `door` di
                                             WHERE di.`name` = d.`name`
                                                   AND di.`inout` = 0
                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                   AND NOT EXISTS (SELECT *
                                                                          FROM `door` dii
                                                                          WHERE dii.`name` = di.`name`
                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL 1 DAY)
                                                                                AND dii.`timestamp` < di.`timestamp`
                                                                                AND dii.`inout` = 1))
             UNION ALL
             SELECT timestampadd(second, -1 * second(d.`timestamp`), timestampadd(minute, -1 * minute(d.`timestamp`), timestampadd(hour, -1 * hour(d.`timestamp`), d.`timestamp`))) `timestamp_in`,
                    d.`timestamp` `timestamp_out`,
                    d.`name`
                    FROM `door` d
                    WHERE d.`inout` = 0
                          AND NOT EXISTS (SELECT *
                                                 FROM `door` di
                                                 WHERE di.`name` = d.`name`
                                                       AND di.`inout` = 1
                                                       AND di.`timestamp` < d.`timestamp`
                                                       AND date(di.`timestamp`) = date(d.`timestamp`))
                          AND EXISTS (SELECT *
                                             FROM `door` di
                                             WHERE di.`name` = d.`name`
                                                   AND di.`inout` = 1
                                                   AND date(di.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                   AND NOT EXISTS (SELECT *
                                                                          FROM `door` dii
                                                                          WHERE dii.`name` = di.`name`
                                                                                AND date(dii.`timestamp`) = date_add(date(d.`timestamp`), INTERVAL -1 DAY)
                                                                                AND dii.`timestamp` > di.`timestamp`
                                                                                AND dii.`inout` = 0))) x
       WHERE date(x.`timestamp_in`) = '2018-04-02'
              OR date(x.`timestamp_out`) = '2018-04-02'
       ORDER BY x.`name`,
                x.`timestamp_in`;

db<>fiddle