在SQL中执行pivot-table-ish JOIN

时间:2011-06-17 15:27:41

标签: sql join subquery left-join

我的雇主有一个批处理计算群集,用于处理用户提交的作业。每个批处理作业包含三个步骤:

  1. 工作开始
  2. 工作完成
  3. 结果报告给用户
  4. 批处理作业管理软件会在每个步骤发生时记录,日志文件由一个元组组成,该元组包含提交作业的员工的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条目用于同一员工的未来工作。所以报告看起来像:

    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,启动”,但这并不一定会选择“正确”的行。 “GROUP BY”报告的大多数行都是错误的。

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

1 个答案:

答案 0 :(得分:2)

问题在于您加入finishreport

的方式

您不希望start.timestamp < finish.timestamp您真正想要start.timestamp < MIN(finish.timestamp)

当然这不起作用所以你必须在加入后这样做。

e.g。

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

你也可以将开头转换为内连接,因为没有开始就完成任务就没有多大意义