Создать итоговую строку для данных в нескольких таблицах - PullRequest
9 голосов
/ 17 марта 2011

Я пытаюсь написать SQL-запрос, чтобы сгенерировать итоговую строку для действий, выполняемых данным пользователем за указанный период. У меня есть следующая соответствующая структура таблицы:

пользователи

  • ID
  • команда

audit_periods (может быть обработка, доставка, перерыв и т. Д.)

  • user_id
  • period_type (может быть «обработка», «доставка» и т. Д. - в настоящее время не нормализовано)
  • started_at
  • finish_at (может быть нулевым для текущего периода, следовательно, логика примерно раз ниже)

audit_tasks

  • audit_period_id
  • audit_task_type_id
  • created_at
  • счет

audit_task_types

  • name ("scan", "place_in_pallet" и т. Д.)
  • оценка (кажется излишней, но нам нужно сохранить оценку, полученную на Audit_task во время выполнения, так как оценка Audit_task_type может измениться позже)

ER Diagram

Для каждого пользователя за данный период я ​​хотел бы создать что-то вроде следующего ряда данных:

users.id users.email time_spent_processing time_spent_shipping ... number_of_scans number_of_pallets

, который будет рассчитан для каждого пользователя:

  • Какие audit_periods хотя бы частично попадают в нужное окно? (Использует start_at и finish_at.)
  • Сколько времени пользователь провел в каждом типе audit_period? (Я хотел бы представить группу по параметру audit_periods.period_type.)
  • Какие Audit_tasks попадают в желаемое окно? (Использует созданный_кат - пока нет в коде ниже.)
  • Сколько из каждого типа audit_task пользователь выполнил во время окна? (Присоединяется к Audit_task_type и, вероятно, включает группу по Audit_task_types.name.)
  • Сколько очков было заработано за период времени? (Суммирует оценки всех заданий audit_t в окне.)

Я исчерпал все известные мне уловки SQL (не так много) и придумал что-то вроде следующего:

select 
    u.id as user_id,
    u.email as email,
    u.team as team,
    ap.period_type as period_type,
    att.name,
    time_to_sec(
      timediff(least("2011-03-17 00:00:00", ifnull(ap.finished_at, utc_timestamp())), greatest("2011-03-16 00:00:00", ap.started_at))
    ) as period_duration,
    sum(at.score) as period_score
  from audit_periods as ap
  inner join users as u on ap.user_id = u.id
  left join audit_tasks as at on at.audit_period_id = ap.id
  left join audit_task_types as att on at.audit_task_type_id = att.id
  where (ap.started_at >= "2011-03-16 00:00:00" or (ap.finished_at >= "2011-03-17 00:00:00" and ap.finished_at <= "2011-03-17 00:00:00"))
    and (ap.finished_at <= "2011-03-17 00:00:00" or (ap.started_at >= "2011-03-16 00:00:00" and ap.started_at <= "2011-03-16 00:00:00"))
    and u.team in ("Foo", "Bar")
  group by u.id, ap.id, at.id

но это, по-видимому, функционально эквивалентно простому выбору всех задач аудита в конце. Я пробовал также несколько подзапросов, но безрезультатно. Более непосредственно, это генерирует что-то вроде (пропуская менее важные столбцы):

user_id   |   period_type   |   period_duration  |  name            |   score
1             processing        1800s               scan                200
1             shipping          1000s               place_in_pallet     100
1             shipping          1000s               place_in_pallet     100
1             break             500s                null                null

когда я хочу:

user_id   |   processing    |   shipping  |  break  |  scan  |  place_in_pallet  |  score
1             1800s             1000s        500s      1        2                   400

Я могу легко получить все Audit_tasks для данного пользователя и свернуть их в коде, но я могу получить сотни тысяч Audit_tasks за определенный период, поэтому это нужно сделать в SQL.

Просто чтобы прояснить ситуацию - я ищу запрос для генерации одной строки на пользователя, содержащей сводные данные, собранные по трем остальным таблицам. Итак, для каждого пользователя я хочу знать, сколько времени он провел в каждом из типов Audit_period (обработка 3600 секунд, доставка 3200 секунд и т. Д.), А также, сколько из каждого выполненного им задания Audit_task (5 сканирований, 10 размещенных в поддон и т. д.).

Я думаю, что у меня есть элементы решения, у меня просто проблемы с их объединением. Я точно знаю, как это сделать в Ruby / Java / и т. Д., Но я не думаю, что достаточно хорошо понимаю SQL, чтобы понять, какой инструмент мне не хватает. Нужна ли временная таблица? Союз? Какая-то другая конструкция целиком?

Любая помощь очень ценится, и я могу уточнить, является ли все это полным вздором.

1 Ответ

1 голос
/ 17 марта 2011

Вам нужно будет разбить это на два перекрестных запроса, которые предоставят вам информацию о audit_periods по пользователю, и другой запрос, который даст вам информацию Audit_task по пользователю, а затем присоединить ее к таблице Users. Не ясно, как вы хотите свернуть информацию в каждом из случаев. Например, если у данного пользователя есть 10 audit_period строк, как запрос должен свести эти длительности? Я предположил сумму длительностей здесь, но вы могли бы хотеть мин или макс или, возможно, даже общую дельту.

Select U.user_id
    , AuditPeriodByUser.TotalDuration_Processing As processing
    , AuditPeriodByUser.TotalDuration_Shipping As shipping
    , AuditPeriodByUser.TotalDuration_Break As break
    , AuditTasksByUser.TotalCount_Scan As scan
    , AuditTasksByUser.TotalCount_Place_In_Pallet As place_in_pallet
    , AuditTasksByUser.TotalScore As score
From users As U
    Left Join   (
                Select AP.user_id
                    , Sum( Case When AP.period_type = 'processing' 
                                Then Time_To_Sec( 
                                        TimeDiff( 
                                            Coalesce(AP.started_at, UTC_TIMESTAMP()), AP.finished_at ) ) ) 
                        As TotalDuration_Processing
                    , Sum( Case When AP.period_type = 'shipping' 
                                Then Time_To_Sec( 
                                        TimeDiff( 
                                            Coalesce(AP.started_at, UTC_TIMESTAMP()), AP.finished_at ) ) ) 
                        As TotalDuration_Shipping
                    , Sum( Case When AP.period_type = 'break' 
                                Then Time_To_Sec( 
                                        TimeDiff( 
                                            Coalesce(AP.started_at, UTC_TIMESTAMP()), AP.finished_at ) ) ) 
                        As TotalDuration_Break
                From audit_periods As AP
                Where AP.started_at >= @StartDate 
                    And AP.finished_at <= @EndDate
                Group by AP.user_id
                ) As AuditPeriodByUser
            On AuditPeriodByUser.user_id = U.user_id
    Left Join   (
                Select AP.user_id
                    , Sum( Case When AT.Name = 'scan' Then 1 Else 0 End ) As TotalCount_Scan
                    , Sum( Case When AT.Name = 'place_in_pallet' Then 1 Else 0 End ) As TotalCount_Place_In_Pallet
                    , Sum( AT.score ) As TotalScore
                From audit_tasks As AT
                    Join audit_task_types As ATT
                        On ATT.id = AT.audit_task_type_id
                    Join audit_periods As AP
                        On AP.audit_period_id = AP.id
                Where AP.started_at >= @StartDate 
                    And AP.finished_at <= @EndDate
                Group By AP.user_id
                ) As AuditTasksByUser
        On AuditTasksByUser.user_id = U.user_id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...