Friday, 23 August 2013

How to update_all() with subquery referring to the record being updated in Rails

How to update_all() with subquery referring to the record being updated in
Rails

I'm trying to implement an equivalent of the following (postgres)SQL
statement in rails:
UPDATE events AS A
SET A.has_repeated = EXISTS(SELECT *
FROM events AS B
WHERE A.event_type_id = B.event_type_id
AND B.started_at > A.finished_at
AND B.started_at - A.finished_at <= 7)
That is I need to mark all events that have an event of the same
event_type_id having happened within 7 days.
I want to implement is as a class method of Event that I could use like:
Event.where( some conditions ).limit( ... ).offset( ...).mark_repeats()
so the generated SQL would have corresponding WHERE, LIMIT and OFFSET
clauses appended.
I started with a broken implementation:
def Event.mark_repeats
update_all( 'has_repeated = EXISTS( ...etc... )' )
end
but then I couldn't find where to specify the first table alias (A). I
tried with:
def Event.mark_repeats
from('events AS A').update_all( 'has_repeated = EXISTS( ...etc... )' )
end
but it didn't work.
My current implementation is:
def Event.mark_repeats
sql = "UPDATE events AS A SET has_repeated = #{repetition_criterion_sql}"
if current_scope
sql += "\n#{current_scope.where_sql.try(:gsub,/"events"/,'A')}"
end
connection.update_sql sql
end
which doesn't respect limit or offset and feels generally fragile,
especially the way I have to replace "events" with the alias.
Is there a better way to accomplish this - a different SQL technique to
accomplish the same end, or a different rails abstraction to express the
problem in?

No comments:

Post a Comment