Вложенные запросы в Arel - PullRequest
19 голосов
/ 24 мая 2010

Я пытаюсь вложить запросы 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) для выполнения вышеуказанного? Может быть, есть совершенно другой способ выполнить этот запрос, который не использует вложенные запросы?

Ответы [ 5 ]

24 голосов
/ 12 октября 2011

Вот мой подход к временным таблицам и Арелю. Он использует 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, etc ...

inner_query ->

select * from your_models where stuff='foo'

external_query ->

select * from (select * from your_models where stuff='foo') as results;
8 голосов
/ 24 мая 2010

Вопрос в том, зачем вам нужен «вложенный запрос»?Нам не нужно использовать «вложенные запросы», это мышление в мышлении 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)

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

Здесь проекция 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 Я могу сказать, что вы начинаете знать, как, поскольку вы использовали Таблицу (: точки), а не модель ActiveRecord Точка.

7 голосов
/ 20 июля 2012
Point.
 from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")).
 select("sorted.*").
 group("sorted.client_id")
7 голосов
/ 30 декабря 2010

Хотя я не думаю, что эта проблема требует вложенных запросов, как упоминал 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))))
#
1 голос
/ 23 августа 2014

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

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

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

...