делать сводную таблицу JOIN в SQL - PullRequest
5 голосов
/ 17 июня 2011

У моего работодателя есть кластер пакетных вычислений, который обрабатывает задания, отправленные пользователями. Каждое пакетное задание состоит из трех этапов:

  1. работа началась
  2. работа завершена
  3. результаты, сообщенные пользователю

Программное обеспечение управления пакетными заданиями регистрирует, когда выполняется каждый из этих этапов, и файл журнала состоит из кортежа с идентификационным кодом сотрудника, который отправил задание, какой этап произошел и отметка времени, когда он произошел. В 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, потому что мне также нужно идентифицировать задания, которые были запущены, но не были завершены или о которых было сообщено.

Это работает довольно хорошо. Это дает мне строки, которые мне нужны. Но это дает мне много ложных строк, потому что СОЕДИНЕНИЯ соответствуют записям finish и report для будущих заданий того же сотрудника в дополнение к текущему заданию. Таким образом, отчет выходит в виде:

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

Легко распознать паразитные строки: каждое задание запускается только один раз, поэтому при сортировке правильная строка - это первая строка с уникальным значением «start». Я сейчас работаю над проблемой паразитных рядов на уровне приложения, просто пропуская паразитные ряды, но это просто, ну, не очень элегантно. И это дорого: некоторые из этих сотрудников представили десятки рабочих мест, поэтому в настоящее время результаты моих запросов составляют около 15% законных записей и 85% поддельных. Это много потерянного времени, пропускающего фиктивные записи. Было бы хорошо, если бы у каждой работы был уникальный идентификатор, но у меня просто нет этих данных.

Мне нужно каким-то образом ограничить JOIN, чтобы он выбирал только одну запись «Закончено» и «Сообщено» для каждой записи «Начало»: единственная запись, минимальная временная метка которой больше, чем временная метка предыдущего шага. Я попытался сделать это, используя подзапрос в качестве таблицы, к которой я присоединился, но я не мог понять, как это сделать без коррелированного подзапроса. Я также попытался сделать это с помощью «GROUP BY employee-ID, start», но это не обязательно выбрало «правильный» ряд. Большинство строк, указанных в сообщении «GROUP BY», были неправильными.

Итак, гуру SQL, возможно ли сообщить только те строки, которые мне нужны? И если да, то как? Я сейчас использую sqlite3, но при необходимости могу перенести базу данных в MySQL.

1 Ответ

2 голосов
/ 17 июня 2011

Проблема в том, как вы присоединяетесь к finish и report

Вы не хотите 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

Также вы, вероятно, могли бы преобразовать начало во внутреннее соединение, так как не имеет смысла иметь финиш без начала

...