2010-05-24 28 views
19

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

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

un alias para la subconsulta se pueden crear haciendo

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

pero luego me pegan como la forma de pasarlo a la consulta de los padres (a pedir #to_sql, que suena bastante feo) .

¿Cómo se utiliza una instrucción SELECT como una sub consulta en Arel (o registro activo) para lograr lo anterior? ¿Tal vez hay una forma completamente diferente de lograr esta consulta que no utiliza consultas anidadas?

Respuesta

8

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

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 la indicación de fecha y hora client_id es MUY importante ya que no podemos proyectar todos los dominios de la relación (es decir, ordenado. *). Debe proyectar específicamente todos los dominios que se usarán dentro de la operación de agrupación para la relación. La razón es que no hay ningún valor para * que sea claramente representativo de un client_id agrupado. Por ejemplo supongamos que tiene la siguiente tabla

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

Aquí si el grupo no se podía realizar una proyección en el dominio puntuación porque el valor podría ser o bien 27 o 69, pero se podría proyectar una suma (puntuación)

Solo puede proyectar los atributos de dominio que tienen valores únicos para el grupo (que generalmente son funciones agregadas como suma, máximo, mínimo). Con su consulta no importaría si los puntos fueron 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.

Háganme saber cómo puedo ayudarlo con Arel. Además, he estado trabajando en una serie de aprendizaje para que la gente use Arel en su núcleo. La primera de la serie está en http://Innovative-Studios.com/#pilot . Puedo decir que están empezando a saber cómo usar Table (puntos) en lugar del Punto de modelo ActiveRecord.

+0

Gracias por la respuesta detallada. "la orden de la marca de tiempo es irrelevante ya que no hay una sola marca de tiempo que pueda representar una agrupación". Tienes razón; Veo lo que dices. Parece que MySQL soluciona esta incoherencia devolviendo solo la primera fila del grupo client_id, que es lo que estaba buscando. Ahora veo que este no es un comportamiento con el que debería contar. Mi objetivo es devolver el punto más reciente para todos los client_ids, es decir, un único punto con la marca de tiempo máxima por cada agrupación client_id. Es importante hacerlo en una consulta porque se sondeará con frecuencia. – Schrockwell

+0

Necesitaríamos usar alguna función agregada. Si nos preguntamos "¿Qué estamos tratando de hacer?" La respuesta sería encontrar la fecha más reciente o "máxima" para que pasemos max (timestamp) en sql. Esto correspondería a Arel :: Attribute :: Expression :: Maximum que puede invocarse con azúcar sintáctico en un Arel :: Attribute sort sort [: timestamp] .maximum(). Hay una advertencia. Asegúrese de agregar la marca de tiempo para agrupar la operación #group ('client_id, timestamp') o todo el escenario de agrupación generará un error. Sé que la función de agregado MAX funciona en fechas en Postgres y estoy seguro en MySQL también. – Snuggs

+1

En primer lugar, la ordenación y el orden no son parte del álgebra relacional. Arel lo define de todos modos. En segundo lugar, si las subconsultas son parte del álgebra relacional es irrelevante. Conceptualmente, el resultado de un SELECT no es visible hasta que se ejecuta la cláusula WHERE. Por lo tanto, no todas las bases de datos (por ejemplo, Postgres) permiten alias de columna en las cláusulas WHERE y en su lugar dependen de subconsultas. Si Arel no puede manejar subconsultas, los nombres en la cláusula WHERE no pueden ser alias. Esto puede ser complicado cuando no puede depender de Arel para generar nombres. –

7

Aunque no creo que este problema necesite consultas anidadas, como se mencionó Snuggs. Para aquellos que necesitan consultas anidadas. Esto es lo que tengo trabajo hasta el momento, no es genial, 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)))) 
# 
+0

Solución más pura, el truco es 'Arel :: SqlLiteral' allí –

+0

Pequeña corrección, en lugar de usar' '' Arel :: SqlLiteral''', la correcta sería '' 'Arel :: Nodes :: SqlLiteral''' – jonathanccalixto

23

Aquí es 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 puedes hacer algunas cosas agradables con outer_query, paginate, select, group, etc ...

inner_query ->

select * from your_models where stuff='foo' 

outer_query ->

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

Puede también obtiene una outer_query sin tener que especificar un modelo falso o nombre de tabla.Las dos últimas líneas de la lista anterior pueden reemplazarse por esta línea, que es lo que "de" llama de todos modos: outer_query = Arel :: SelectManager.new (Arel :: Table.engine, Arel.sql ("(# { inner_query.to_sql}) como resultados ")) – DSimon

+1

Este es el legendario Mr.Todd – beck03076

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

Para hacer esto en "puro" Arel, 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 ordenados serían recuperados y la cola orificio del modelo Puntos en lugar de fabricado como se indica anteriormente.

Cuestiones relacionadas