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