2013-06-23 1 views
1

У меня есть таблица с именем acts, и я хочу запустить запрос, который обновляет значения act в течение целой недели. Я хотел бы убедиться, что запрос всегда возвращает одну строку для каждого дня недели, даже если в этот день нет записей. Сейчас я делаю это так:Rails с именем scope для левой объединенной модели

def self.this_week_totals 
    sunday = Time.now.beginning_of_week(:sunday).strftime("%Y-%m-%d") 
    connection.select_values(<<-EOQ) 
     SELECT COALESCE(SUM(end_time - start_time), '0:00:00') AS total_time 
     FROM generate_series(0, 6) AS s(t) 
     LEFT JOIN acts 
     ON  acts.day = '#{sunday}'::date + s.t 
     WHERE deleted_at IS NULL 
     GROUP BY s.t 
     ORDER BY s.t 
    EOQ 
    end 

Есть ли какой-нибудь способ, которым я мог бы сделать это именованный от класса Act, поэтому он может быть объединен с другими условиями, например, для фильтрации актов по client_id ? Поскольку acts не находится в моем FROM, но является частью LEFT JOIN, я предполагаю, что нет, но, возможно, кто-то там знает способ.

Редактировать: Чтобы уточнить, цель в этом методе всегда возвращает ровно 7 объектов закона, независимо от того, что находится в базе данных.

ответ

2

, если вы хотите, чтобы ваш объект запроса, чтобы быть в цепочке должно быть объектом ActiveRelation

where, select, order и другие объекты Arel возвращают объекты ActiveRelation, которые в цепочке, так что если ниже работает вы можете цепи прочь возвращенного объект запрос

нота в рельсах 3 и выше, имеющий метод класса, который возвращает ActiveRelation в основном такие же, как объем, они являются объектами как цепной запрос

class Act 
    def self.this_week_totals 
    sunday = Time.now.beginning_of_week(:sunday).strftime("%Y-%m-%d") 
    select("COALESCE(SUM(end_time - start_time), '0:00:00') AS total_time") 
     .from("generate_series(0, 6) AS s(t)") 
     .joins("LEFT JOIN acts ON acts.day = '#{sunday}'::date + s.t") 
     .where("deleted_at IS NULL") 
     .group("s.t") 
     .order("s.t") 
    end 
    # ... 
end 

client_id = params[:client_id] 
Act.this_week_totals.where("client_id = ?", client_id) 

http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-from

+0

На самом деле, если вы сделаете ПРАВОЕ СОЕДИНЕНИЕ, оно работает! Я всегда игнорировал ПРАВИЛЬНЫЕ СОЕДИНЕНИЯ, так как они тривиальны для повторной записи как LEFT JOINs, но вот случай, когда мне это нужно. Если вы отредактируете свой ответ, я приму его. :-) –

+0

обновлено до RIGHT JOIN – house9

+0

Хм, одно предостережение с этим подходом заключается в том, что если я добавлю еще одно соединение, скажем в таблицу 'clients', строки начнут исчезать. Это связано с тем, что я присоединяюсь к «клиентам», где «clients.id = acts.client_id», а RIGHT JOIN делает «client_id» NULL, поэтому сравнение является ложным. Это не проблема, когда первичная таблица является 'generate_series', и я LEFT JOINING для подзапроса, который объединяет' act' и 'clients'. Я не уверен, как использовать INNER JOIN для клиентов, в то время как RIGHT JOINING - для генерации_серий, и все равно всегда получается 7 строк. –

1

Хотя я действительно думал, что я мог бы использовать решение от @ house9, я не вижу какой-либо способ, чтобы избежать ущерба, по меньшей мере, одну из этих целей:

  1. Всегда уступайте 7 объектов Act.
  2. Верните ActiveRelation, чтобы я мог составить этот метод с другими областями.
  3. Разрешение на подключение к таблице clients.

Вот часть-SQL/решение часть-рубин я на самом деле, используя, который, к сожалению, дает на пункт № 2 выше, а также возвращает кортежи, а не актами:

def self.this_week(wk=0) 
    sunday = Time.now.beginning_of_week(:sunday) 
    sunday += wk.weeks 
    not_deleted.where("day BETWEEN ? AND ?", sunday, sunday + 7.days) 
end 

scope :select_sum_total_hours, 
    select("EXTRACT(EPOCH FROM COALESCE(SUM(end_time - start_time), '0:00:00'))/3600 AS total_hours") 

scope :select_sum_total_fees, 
    joins(:client). 
    select("SUM(COALESCE(clients.rate, 0) * EXTRACT(EPOCH FROM COALESCE(end_time - start_time, '0:00:00'))/3600) AS total_fees") 


def self.this_week_totals_by_day(wk=0) 
    totals = Hash[ 
    this_week(wk) 
     .select("EXTRACT(DAY FROM day) AS just_day") 
     .select_sum_total_hours 
     .select_sum_total_fees 
     .group("day") 
     .order("day") 
     .map{|act| [act.just_day, [act.total_hours.to_f, act.total_fees.to_money]]} 
    ] 
    sunday = Time.now.beginning_of_week(:sunday) 
    sunday += wk.weeks 
    (0..6).map do |x| 
    totals[(sunday + x.days).strftime("%d")] || [0, 0.to_money] 
    end 
end 

Это может быть Немного похудел, и это приведет к ошибкам, если бы был месяц с менее чем 7 дней, но, надеюсь, он показывает, что я делаю. Области для this_week, select_sum_total_hours и select_sum_total_fees используются в другом месте, поэтому я хочу вытащить их в области, а не повторять их в нескольких больших исходных строках SQL.