[英]Joining three tables (2 level) and finding sum based on conditions - 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查詢加入tasks表來獲取結果,因為這些數據只在一個地方使用而很少使用。
但現在我需要按月對小時數進行分組,並找出每個用戶記錄可計費和非計費小時數的小時數。 例如。
結果:
-----------------------------------------------
user_id | month | BillableHrs | NonBillableHRS|
-----------------------------------------------
我嘗試過group(user_id, MONTH(date))
但結果很奇怪。 如何獲取此類信息?
順便說一句,將連接更改為:
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'
我會做這樣的事情:
#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
因為這是一些非常丑陋的代碼,如果它表示為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})
再一次,愚蠢的錯字可能。
將其推送到數據庫是非常有效的,遠比嘗試通過Rails運行它更有效。 一個不錯的查詢優化器不會在視圖中評估不需要的表達式,甚至不會執行邏輯冗余的連接。
無論如何,只是浮出來征求意見。 我知道將業務邏輯放在數據庫層並不符合每個人的口味,但它會讓它干澀,我在自己的應用程序中有情況,出於性能原因,它絕對是唯一的選擇。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.