2011-03-17 119 views
9

我正在尝试编写一个SQL查询以生成给定用户在给定时段执行的操作的汇总行。我有以下相关的表结构:为多个表中的数据创建汇总行

用户

  • ID

audit_periods(可处理,运输,休息等)

  • USER_ID
  • period_type(可以是“处理”,“sh ipping”等 - 当前未归一化)
  • started_at
  • finished_at(可以为空当期,因此围绕下面倍逻辑)

audit_tasks

  • audit_period_id
  • audit_task_type_id
  • created_at
  • 得分

audit_task_types

  • 名称( “扫描”, “place_in_pallet” 等)
  • 得分(似乎是多余的,但我们需要保持得分,在它被执行的时候收到的audit_task作为audit_task_type得分以后可以更改)

ER Diagram

对于每个用户对于给定的牙周d,我想创造这样的数据的一行:

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

这会搞清楚每个用户来计算:

  • 什么audit_periods至少部分落在所需的窗口? (使用started_at和finished_at。)
  • 用户在每种类型的audit_period中花费了多长时间? (应该涉及到audit_periods.period_type组,我想象一下。)
  • 什么audit_tasks属于所需的窗口? (使用created_at - 尚未在下面的代码中。)
  • 用户在窗口期间完成的每​​种audit_task类型有多少? (加入audit_task_type,可能涉及到一个由audit_task_types.name组成的团队。)
  • 在此期间赚了多少分? (总计窗口中所有audit_tasks的分数。)

我已用尽了所有的招数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中完成。

只是要清楚 - 我正在寻找一个查询来为每个用户生成一行,其中包含在其他3个表中收集的摘要数据。因此,对于每个用户,我想知道他在每种类型的audit_period(3600秒处理,3200秒运输等)中花了多少时间,以及他执行的每个audit_task有多少次(5次扫描,10个项目放置在托盘等)。

我想我有一个解决方案的元素,我只是无法将它们拼接在一起。我确切地知道我会如何在Ruby/Java /等中实现这一点,但我不认为我理解SQL足以知道我错过了哪个工具。我需要临时表吗?工会?其他一些构造完全?

任何帮助,非常感谢,我可以澄清,如果上述是完全废话。

+0

我暂时删除了我的帖子,因为它发生在我身上,还有更多我们需要知道。目前尚不清楚如何找到“可以加工”的任务。我们需要更多地了解表格的结构。如何在模式中实际定义“time_spent_shipping”?什么是“扫描”,它们存储在哪里?托盘计数如何存储等 – Thomas 2011-03-17 06:05:43

+0

顺便说一句,您的查询和我的每个用户每个周期返回一行的原因是您(和我)正在Audit_Period.Id和Audit_Tasks.Id上分组。假设Id是表格的PK,那么您将为每个表格返回一行。 – Thomas 2011-03-17 06:07:44

+0

@Thomas - 我编辑了这个问题来更好地阐明表格结构。希望这已经足够了,但如果不是,我可以再刺一次。我明白为什么我们的查询返回多行。我不明白的部分是如何有效地将这些行中包含的信息合并到一行中。我猜测我有一种我以前从未见过的伎俩,或者我可以忽略的东西。 – Kyle 2011-03-17 06:16:36

回答

1

您将需要将其分解为两个交叉表查询,它们可以为用户提供有关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 
+0

@凯尔 - 我已经修改了我的解决方案给你的新信息。 – Thomas 2011-03-17 15:25:56

+0

我会试试看。但是另外一个后续问题是:有没有办法使用group by(或某物)来提取相关的period_types和task_types,而不必根据具体情况显式声明它们?对于记录,audit_period持续时间的总和是期望的行为,以及audit_tasks的计数。不过,你的解决方案会让我开始。谢谢! – Kyle 2011-03-17 15:39:52

+0

@Kyle - 动态确定列称为动态交叉表。 (我所介绍的内容通常被称为静态交叉表)使用SQL语言没有原生的方法。相反,您应该在数据库之外组合该类型的SQL语句。 – Thomas 2011-03-17 15:56:17