sql - 在 SQL 中执行数据透视表的 JOIN

标签 sql join subquery left-join

我的雇主有一个批处理计算集群来处理用户提交的作业。每个批处理作业由三个步骤组成:

  • 工作开始
  • 作业完成
  • 结果报告给用户

  • 批处理作业管理软件会记录这些步骤中的每一个发生的时间,并且日志文件包含一个元组,其中包含提交作业的员工的 ID 代码、发生的步骤以及发生时间的时间戳。在 CSV 中,它看起来像:
    ID  step    timestamp
    --  ------  ---------
    A   start   3533
    B   start   3538
    B   finish  3549
    C   start   3551
    A   finish  3557
    B   report  3559
    C   finish  3602
    A   report  3603
    B   start   3611
    C   report  3623
    B   finish  3643
    B   report  3657
    

    等等。

    该数据集的另一个特点是员工之间存在共识,但员工内部不存在共识;即,每个员工必须等到他们当前的工作报告后才能开始下一份工作。因此,当我按日期排序并将结果限制为单个员工时,记录总是以“开始”、“完成”、“报告”的顺序出现。

    我想创建一个数据透视表,将每个工作分成一行。于是上面的数据变成了:
    employee-ID  started  finished  reported
    -----------  -------  --------  --------
    A            3533     3557      3603
    B            3538     3549      3559
    B            3611     3643      3657
    C            3551     3602      3623
    

    所以,进入SQL:
    SELECT
        log.ID AS employee-ID,
        start.timestamp AS started,
        finish.timestamp AS finished,
        report.timestamp AS reported
    FROM
        log
    
        LEFT OUTER JOIN log start ON
        log.ID = start.ID
            AND start.step = 'start'
    
        LEFT OUTER JOIN log finish ON
        log.ID = finish.ID
            AND finish.step = 'finish'
            AND start.timestamp < finish.timestamp
    
        LEFT OUTER JOIN log report ON
        log.ID = report.ID
            AND report.step = 'report'
            AND finish.timestamp < report.timestamp
    
    ORDER BY employee-ID,started,finished,reported;
    

    我确实需要 LEFT OUTER JOIN,因为我还需要确定已启动但尚未完成或报告的作业。

    这很好用。它确实给了我需要的行。但它给了我很多虚假的行,因为 JOIN 匹配 finishreport除当前工作外,同一员工 future 工作的条目。所以报告出来看起来像:
    employee-ID  started  finished  reported
    -----------  -------  --------  --------
    A            3533     3557      3603
    B            3538     3549      3559
    B            3538     3549      3657 <-- spurious
    B            3538     3643      3657 <-- spurious
    B            3611     3643      3657
    C            3551     3602      3623
    

    识别伪行很容易:每个作业只启动一次,因此给定排序,正确的行是具有唯一“开始”值的第一行。我现在正在应用程序级别通过跳过虚假行来解决虚假行问题,但这似乎不雅。而且代价高昂:其中一些员工提交了数十个作业,因此目前,我的查询结果大约是 15% 的合法条目和 85% 的虚假条目。跳过虚假条目浪费了很多时间。如果每个工作都有一个唯一的 ID,那就太好了,但我只是没有这些数据。

    我需要以某种方式限制 JOIN,以便它为每个“开始”条目仅选取一个“完成”和“报告”条目:最小时间戳大于前一步骤时间戳的单个条目。我尝试通过使用子查询作为我要加入的表来做到这一点,但是如果没有相关的子查询,我无法弄清楚如何做到这一点。我也尝试通过使用“GROUP BY employee-ID,started”来做到这一点,但这不一定选择“正确”的行。 “GROUP BY”报告的大多数行都是错误的。

    那么,SQL 大师,是否可以只报告我需要的行?如果是这样,如何?我现在正在使用 sqlite3,但如果需要,可以将数据库传输到 MySQL。

    最佳答案

    问题是你如何加入 finishreport
    你不想要 start.timestamp < finish.timestamp你真的想要start.timestamp < MIN(finish.timestamp)
    当然这行不通,所以你必须在加入后做。

    例如

    SELECT
        log.ID AS employee_ID,
        start.timestamp AS started,
        MIN(finish.timestamp) AS finished,
        MIN(report.timestamp) AS reported
    FROM
        log
    
    
    LEFT OUTER JOIN log start ON
    log.ID = start.ID
        AND start.step = 'start'
    
    LEFT OUTER JOIN log finish ON
    log.ID = finish.ID
        AND finish.step = 'finish'
        AND start.timestamp < finish.timestamp
    
    LEFT OUTER JOIN log report ON
    log.ID = report.ID
        AND report.step = 'report'
        AND finish.timestamp < report.timestamp
    
    GROUP BY log.ID,
        start.timestamp 
    ORDER BY 
        employee_ID,started,finished,reported
    

    您也可以将开始转换为内部连接,因为没有开始就结束并没有多大意义

    关于sql - 在 SQL 中执行数据透视表的 JOIN,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6388093/

    相关文章:

    mysql - 在 mysql 中限制要连接的行数

    join - Oozie fork 在一个被杀死时杀死所有 Action

    sql-server - MSSQL JOIN ON GROUP BY 太慢

    mysql - SQL 连接表到选定的记录

    sql-server - 多个 on 语句如何在单个内部联接中工作?

    mysql - Geo SQL 查找位置附近的点

    MySql触发器,在插入之前删除同一个表中的一行

    mysql - NOT IN 在我的查询中不起作用

    mysql - 提高MySQL子查询的性能

    围绕间隙的 SQL 分组