2013-12-10 18 views
0

我有以下模式:加入三個表(2級),並發現和基於條件 - SQL的Rails

User: 
--- 
ID 
--- 

Tasks: 
------------------- 
ID | classification 
------------------- 

Timesheets: 
------------------------ 
ID | task_id | user_id 
------------------------ 

TimesheetItem: 
-------------------------------- 
ID | timesheet_id | hours | date 
-------------------------------- 

協會:

class User 
    has_many :timesheets 
end 

class Task 
    has_many :timesheets 
end 

class Timesheet 
    has_many :timesheet_items 
    belongs_to :user 
    belongs_to :task 
end 

class TimesheetItem 
    belongs_to :timesheet 
end 

分類可以是 「收費」 或「非計費」。 現在我需要找到每個用戶的計費和非計費時間的這樣的總和:

----------------------------------- 
|user_id | billable | nonbillable | 
----------------------------------- 

我在Rails有什麼:

User.joins(:timesheets, :timesheets => :task, :timesheets => :timesheet_items) 
    .select("SUM(CASE 
        WHEN tasks.task_classification = 'Billable' 
         THEN timesheet_items.hours 
        ELSE 
         0 
       END) as billable, 
      SUM(CASE 
        WHEN tasks.task_classification = 'Non-Billable' 
        THEN timesheet_items.hours 
        ELSE 0 
       END) as nonbillable") 

但MySQL允許誤差「tasks.classification 「是一個未知的專欄。查看正在生成的查詢是可以理解的:

SELECT SUM(CASE WHEN tasks.classification = 'Billable' THEN hours ELSE 0 END) as billable, SUM(CASE WHEN tasks.classification = 'Non-Billable' THEN hours ELSE 0 END) as nonbillable FROM `users` INNER JOIN `timesheets` ON `timesheets`.`user_id` = `users`.`id` INNER JOIN `timesheet_items` ON `timesheet_items`.`timesheet_id` = `timesheets`.`id` 

正如您所看到的任務表沒有被連接。

我該如何做到這一點?謝謝。

編輯:

我還挺與加入的任務表中得到的結果,因爲該數據僅用於在一個地方,很少一個普通的SQL查詢徑自。

但現在我需要按月分組,並查找每個用戶爲可計費和不計費小時記錄了多少小時。例如。

結果:

----------------------------------------------- 
user_id | month | BillableHrs | NonBillableHRS| 
----------------------------------------------- 

我已經試過group(user_id, MONTH(date))但..結果是怪異。我怎樣才能獲得這種信息?

而且BTW,改變加入到:

joins(:timesheets, :timesheets => [:task, :timesheet_items])

解決了柱沒有發現問題:)

伊夫終於到達了此解決方案。任何想法的優化?

SELECT 
    users.id as user_id, 
    users.name as user_name, 
    CONCAT(MONTHNAME(date)," ",YEAR(date)) as month, 
    SUM(CASE 
     WHEN tasks.task_classification = "Billable" 
      THEN hours 
     ELSE 
      0 
     END) as blb_sum, 
    SUM(CASE 
     WHEN tasks.task_classification = "Non-Billable" 
      THEN hours 
     ELSE 
      0 
     END) as nblb_sum 
FROM `users` 
    INNER JOIN `timesheets` ON `timesheets`.`user_id` = `users`.`id` 
    INNER JOIN `timesheet_items` ON `timesheet_items`.`timesheet_id` = `timesheets`.`id` 
    INNER JOIN `tasks` ON `timesheets`.`task_id` = `tasks`.`id` 
WHERE 
    timesheet_items.date >= '2013-11-1' AND 
    timesheet_items.date <= '2013-11-31' 
+1

這是ActiveRecord的和SQL的一個醜陋的組合 - 我只是運行它的SQL語句。 –

+0

大聲笑。是啊,你說得對。我正在考慮這樣做。但是在AR中是否有任何方法來做案例?順便說一句,我用這個普通的SQL。 –

+0

嗯,不,我知道的。我會提出另一個建議作爲答案,只是爲了看看人們對它的看法。 –

回答

1

由於這是一些非常醜陋如果代碼表示爲activerecord和SQL的混合體,則一種想法是將複雜查詢定義爲視圖並將其作爲模型引用。

這是很簡單的事 - 如果這是在用戶級別的一組指標,然後構造線沿線的一個觀點:

create view user_billing_metrics 
as 
select user.id user_id, 
     sum(case ... blah blah) billable_hours, 
     sum(case ... blah blah) unbillable_hours 
from ... 

然後創建一個只讀模型...

class UserBillingMetric < ActiveRecord::Base 

    belongs_to :user, :inverse_of => :user_billing_metric 

    def read_only? 
    true 
    end 

end 

則...

class User < ActiveRecord::Base 

    has_one :user_billing_metric, :inverse_of => :user 

    delegate :billable_hours , :to => :user_billing_metric 
    delegate :unbillable_hours, :to => :user_billing_metric 

    def read_only? 
    true 
    end 
end 

然後,您可以:

u = User.find(...) 

u.billable_hours 

......或者......

u = User.find(...) 
hours= u.user_billing_metric 

可能在那裏犯了一個愚蠢的錯字。

這方面的一個很好的功能是,你可以,例如:

users_to_fire = User.joins(:user_billing_metric). 
        where(:user_billing_metrics => {:billable_hours = 0}) 

再次,啞錯字可能。

這是非常有效的比試圖通過導軌運行該推下來到數據庫,更加如此。一個體面的查詢優化器不會在不需要的視圖計算表達式,甚至加入執行邏輯上是多餘的。

總之,只要浮動了這一點徵求意見。我知道,把業務邏輯在數據庫層是不是每個人的口味,但它一直是乾的,我有我自己的應用程序的情況下由於性能原因它是絕對的唯一選擇。

+0

哇。我有點被這個解決方案吹走了。 –

+0

請參閱我的編輯以幫助我。謝謝! :) –

1

我會做這樣的事情:

#id 
class User 
    has_many :timesheets 
    has_many :tasks, :through => :timesheets 

    def billable_hours 
    self.tasks.billable.collect{|task| task.timesheet_items_for(self)}.flatten.collect(&:hours).sum 
    end 

    def non_billable_hours 
    self.tasks.non_billable.collect{|task| task.timesheet_items_for(self)}.flatten.collect(&:hours).sum 
    end 
end 

#id, classification 
class Task 
    has_many :timesheets 

    def timesheet_items_for(user) 
    self.timesheets.for_user(user).collect(&:timesheet_items).flatten 
    end 

    named_scope :billable, :conditions => ["classification = ?", "Billable"] 
    named_scope :non_billable, :conditions => ["classification = ?", "Non-Billable"] 
end 

#id, task_id, user_id 
class Timesheet 
    has_many :timesheet_items 
    belongs_to :user 
    belongs_to :task 

    named_scope :for_user, lambda {|user| {:conditions => ["user_id = ?", user.id]} } 
end 

#id, timesheet_id, hours 
class TimesheetItem 
    belongs_to :timesheet 
end 

然後在你的控制器,假設你已經@user定義,你可以叫@user.billable_hours@user.non_billable_hours

+0

偉大的解決方案! –