2010-05-24 2 views
19

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

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

ответ

8

Вопрос в том, зачем вам нужен «вложенный запрос»? Нам не нужно использовать «вложенные запросы», это мышление в мышлении 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 Я могу сказать, что вы начинаете знать, как с тех пор, как вы использовали таблицу (: points), а не точку модели ActiveRecord.

+0

Благодарим вас за подробный ответ. «заказ временной метки не имеет значения, поскольку нет единой метки времени, которая могла бы представлять группировку». Ты прав; Я вижу, что вы говорите. Похоже, что MySQL работает над этой несогласованностью, возвращая только первую строку группы client_id, к чему я стремился. Теперь я вижу, что это не поведение, на которое я должен рассчитывать. Моя цель - вернуть самую последнюю точку для всех клиентов, т. Е. Одну точку с максимальной меткой времени для каждой группы client_id. Это важно сделать в одном запросе, потому что он будет часто опрошен. – Schrockwell

+0

Нам нужно будет использовать некоторую функцию агрегата. Если мы спросим себя: «Что мы пытаемся сделать?» Ответ будет заключаться в том, чтобы найти самую последнюю или «максимальную» дату, чтобы мы могли передать max (timestamp) в sql. Это соответствовало бы Arel :: Attribute :: Expression :: Maximum, который можно вызвать с помощью синтаксического сахара в атрибуте Arel :: Attribute, например sorted [: timestamp] .maximum(). Есть одно предостережение. Убедитесь, что вы добавили временную метку для групповой операции #group ('client_id, timestamp') или весь сценарий группировки. Я знаю, что функция агрегации MAX работает с датами в Postgres, и я уверен и в MySQL. – Snuggs

+1

Во-первых, сортировка и порядок не являются частью реляционной алгебры. Арель все равно определяет это. Во-вторых, независимо от того, являются ли подзапросы частью реляционной алгебры, не имеет значения. Понятно, что результат SELECT не отображается до тех пор, пока не будет выполнено предложение WHERE. Поэтому не все базы данных (например, Postgres) допускают псевдонимы столбцов в предложениях WHERE и зависят от подзапросов. Если Arel не может обрабатывать подзапросы, тогда имена в предложении WHERE не могут быть сглажены. Это может стать беспорядочным, если вы не можете зависеть от Arel для генерации имен. –

7

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

Чистое решение, трюк 'Arel :: SqlLiteral' там –

+0

Небольшая коррекция, вместо использования' '' Arel :: SqlLiteral''', правильная будет '' 'Arel :: Nodes :: SqlLiteral''' – jonathanccalixto

23

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

outer_query ->

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

Вы можете также получите внешний_query, не указывая поддельную модель или имя таблицы.Последние две строки в вышеперечисленном могут быть заменены этой строкой, что и есть «0»: external_query = Arel :: SelectManager.new (Arel :: Table.engine, Arel.sql ("(# { inner_query.to_sql}) в качестве результатов ")) – DSimon

+1

Это легендарный Mr.Todd – beck03076

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

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

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 

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

 Смежные вопросы

  • Нет связанных вопросов^_^