Подтвердить что ты не робот

Вложенные запросы в Arel

Я пытаюсь вложить SELECT-запросы в Arel и/или Active Record в Rails 3, чтобы сгенерировать следующий оператор SQL.

SELECT sorted.* FROM (SELECT * FROM points ORDER BY points.timestamp DESC) AS sorted GROUP BY sorted.client_id

Псевдоним для подзапроса можно создать, выполнив

points = Table(:points)
sorted = points.order('timestamp DESC').alias

но затем я застрял как передать его в родительский запрос (не дозвонив #to_sql, что звучит довольно уродливо).

Как вы используете инструкцию SELECT в качестве подзапроса в Arel (или Active Record) для выполнения вышеперечисленного? Может быть, есть совсем другой способ выполнить этот запрос, который не использует вложенные запросы?

4b9b3361

Ответ 1

Здесь мой подход к временным таблицам и Arel. Он использует Arel # из метода, проходящего во внутреннем запросе с Arel # to_sql.

inner_query = YourModel.where(:stuff => "foo")
outer_query = YourModel.scoped  # cheating, need an ActiveRelation
outer_query = outer_query.from(Arel.sql("(#{inner_query.to_sql}) as results")).
                          select("*")

Теперь вы можете делать приятные вещи с помощью external_query, paginate, select, group и т.д.

inner_query →

select * from your_models where stuff='foo'

external_query →

select * from (select * from your_models where stuff='foo') as results;

Ответ 2

Вопрос в том, зачем вам нужен "вложенный запрос"? Нам не нужно использовать "вложенные запросы", это мышление в мышлении SQL не реляционной алгебры. С реляционной алгеброй мы выводим отношения и используем вывод одного отношения как входного для другого, чтобы выполнялось следующее:

points = Table(:points, {:as => 'sorted'}) # rename in the options hash
final_points = points.order('timestamp DESC').group(:client_id, :timestamp).project(:client_id, :timestamp)

Лучше всего, если мы оставим переименование в isl, если это абсолютно необходимо.

Здесь проекция client_id AND timestamp ОЧЕНЬ важна, так как мы не можем проецировать все домены из отношения (т.е. отсортированного. *). Вы должны специально спроектировать все домены, которые будут использоваться в операции группировки для отношения. Причина в том, что для * нет значения для *, которое будет отчетливо представлять собой сгруппированный client_id. Например, скажем, что у вас есть следующая таблица

client_id   |   score
----------------------
    4       |    27
    3       |    35
    2       |    22
    4       |    69

Здесь, если вы группируете, вы не можете выполнить проецирование на домен счета, потому что значение может быть 27 или 69, но вы можете проецировать сумму (счет)

Вы можете проектировать только атрибуты домена, которые имеют уникальные значения для группы (которые обычно являются совокупными функциями, такими как sum, max, min). По вашему запросу было бы неважно, были ли точки отсортированы по метке времени, потому что в итоге они были бы сгруппированы по client_id. порядок временной отметки не имеет значения, поскольку нет единой метки времени, которая могла бы представлять группировку.

Пожалуйста, дайте мне знать, как я могу помочь вам с Арелом. Кроме того, я работаю над учебной серией, чтобы люди могли использовать Arel в своей основе. Первая из серий находится в http://Innovative-Studios.com/#pilotЯ могу сказать, что вы начинаете знать, как использовать таблицу Table (: points), а не точку модели ActiveRecord.

Ответ 3

Хотя я не думаю, что эта проблема нуждается в вложенных запросах, как упомянуто Snuggs. Для тех, кому нужны вложенные запросы. Это то, к чему я до сих пор работал, не большой, но он работает:

class App < ActiveRecord::Base   
  has_many :downloads

  def self.not_owned_by_users(user_ids)
    where(arel_table[:id].not_in( 
      Arel::SqlLiteral.new( Download.from_users(user_ids).select(:app_id).to_sql ) ) )
  end
end

class Download  < ActiveRecord::Base
  belongs_to :app
  belongs_to :user

  def self.from_users(user_ids)
    where( arel_table[:user_id].in user_ids )
  end

end

class User < ActiveRecord::Base
  has_many :downloads
end

App.not_owned_by_users([1,2,3]).to_sql #=>
# SELECT `apps`.* FROM `apps` 
# WHERE (`apps`.`id` NOT IN (
#   SELECT app_id FROM `downloads` WHERE (`downloads`.`user_id` IN (1, 2, 3))))
#

Ответ 4

Point.
 from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")).
 select("sorted.*").
 group("sorted.client_id")

Ответ 5

Чтобы сделать это в "чистом" Ареле, это сработало для меня:

points = Arel::Table.new('points')
sorted = Arel::Table.new('points', as: 'sorted')
query = sorted.from(points.order('timestamp desc').project('*')).project(sorted[Arel.star]).group(sorted[:client_id])
query.to_sql

Конечно, в вашем случае точки и сортировка будут извлекаться и настраиваться из модели Points, а не производиться, как указано выше.