ruby-on-rails arel

ruby on rails - Consultas anidadas en Arel



ruby-on-rails (5)

Estoy intentando anidar consultas SELECT en Arel y / o Active Record en Rails 3 para generar la siguiente declaración SQL.

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

Se puede crear un alias para la subconsulta haciendo

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

pero luego me quedo atascado como cómo pasarlo a la consulta principal (a falta de llamar a #to_sql , lo que suena bastante feo).

¿Cómo usa una instrucción SELECT como una sub-consulta en Arel (o Registro Activo) para lograr lo anterior? ¿Es posible que haya una forma completamente diferente de realizar esta consulta que no utilice consultas anidadas?


Aquí está mi acercamiento a las tablas temporales y Arel. Utiliza Arel # del método que pasa en la consulta interna con 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("*")

Ahora puede hacer algunas cosas bonitas con la consulta externa, paginar, seleccionar, agrupar, etc.

consulta_interior ->

select * from your_models where stuff=''foo''

extern_query ->

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


Aunque no creo que este problema necesite consultas anidadas, como mencionó Snuggs. Para aquellos que sí necesitan consultas anidadas. Esto es lo que conseguí trabajando hasta ahora, no excelente, pero funciona:

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)))) #


La pregunta es ¿por qué necesitaría una "consulta anidada"? No necesitamos usar "consultas anidadas", esto es pensar en la mentalidad de SQL, no en el álgebra relacional. Con el álgebra relacional derivamos relaciones y utilizamos la salida de una relación como entrada a otra, por lo que lo siguiente sería cierto:

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

Es mejor si dejamos el cambio de nombre a arel a menos que sea absolutamente necesario.

Aquí, la proyección de client_id AND timestamp es MUY importante ya que no podemos proyectar todos los dominios a partir de la relación (es decir, ordenados. *). Debe proyectar específicamente todos los dominios que se utilizarán dentro de la operación de agrupación para la relación. La razón es que no hay un valor para * que sea claramente representativo de un client_id agrupado. Por ejemplo, digamos que tiene la siguiente tabla

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

Aquí, si agrupas, no puedes realizar una proyección en el dominio de puntuación porque el valor puede ser 27 o 69, pero puedes proyectar una suma (puntuación)

Solo puede proyectar los atributos de dominio que tienen valores únicos para el grupo (que normalmente son funciones agregadas como suma, máximo, mínimo). Con su consulta, no importaría si los puntos estuvieran ordenados por marca de tiempo porque al final se agruparían por client_id. el orden de la marca de tiempo es irrelevante, ya que no hay una sola marca de tiempo que pueda representar una agrupación.

Por favor, dime cómo puedo ayudarte con Arel. Además, he estado trabajando en una serie de aprendizaje para que las personas utilicen Arel en su esencia. El primero de la serie está en http://Innovative-Studios.com/#pilot. Puedo decirle que está comenzando a saber cómo usar la Tabla (: puntos) en lugar del Punto de modelo de ActiveRecord.


Para hacer esto en Arel "puro", esto funcionó para mí:

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

Por supuesto, en su caso, los puntos y los ordenados se recuperarían y adaptarían del modelo de Puntos en lugar de fabricarlos como se indicó anteriormente.


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