2011-03-17 76 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