2009-06-20 9 views
6

Parte de por qué Amo a Rails es que odio el SQL, creo que es más como un lenguaje ensamblador que debe manipularse con herramientas de nivel superior como ActiveRecord. Sin embargo, parece que llegué a los límites de este enfoque y estoy fuera de mi profundidad con el SQL.Consulta de carriles complejos: ¿uniones? Sub-seleccionar? ¿Todavía puedo usar named_scope?

Tengo un modelo complejo con muchos sub-registros. También tengo un conjunto de 30-40 named_scopes que implementan la lógica comercial del cliente. Estos ámbitos se encadenan de forma condicional, por lo que tengo esos ámbitos joins_, por lo que las uniones no se ven afectadas.

Tengo un par de ellas que no funcionan bien, o al menos no como el cliente quiere que trabajen. Aquí hay una idea aproximada de la estructura del modelo, con algunos ámbitos nombrados (no todos necesarios para el ejemplo) que ilustran mi enfoque e indican mis problemas. (Por favor, perdona los errores de sintaxis)

class Man < ActiveRecord::Base 
    has_many :wives 

    named_scope :has_wife_named  lambda { |n| { :conditions => { :wives => {:name => n}}}} 
    named_scope :has_young_wife_named lambda { |n| { :conditions => { :wives => {:name => n, :age => 0..30}}}} 
    named_scope :has_yw_named_v2  lambda { |n| { :conditions => ["wives.name = ? AND wives.age <= 30", n]}} 
    named_scope :joins_wives   :joins => :wives 

    named_scope :has_red_cat   :conditions => { :cats => {:color => 'red'}}   
    named_scope :has_cat_of_color  lambda { |c| { :conditions => { :cats => {:color => c}}}} 
    named_scope :has_7yo_cat   :conditions => { :cats => {:age => 7}} 
    named_scope :has_cat_of_age  lambda { |a| { :conditions => { :cats => {:age => a}}}} 
    named_scope :has_cat_older_than lambda { |a| { :conditions => ["cats.age > ?", a] }} 
    named_scope :has_cat_younger_than lambda { |a| { :conditions => ["cats.age < ?", a] }} 
    named_scope :has_cat_fatter_than lambda { |w| { :conditions => ["cats.weight > ?", w] } } 
    named_scope :joins_wives_cats  :joins => {:wives => :cats} 
end 

class Wife < ActiveRecord::Base 
    belongs_to :man 
    has_many :cats 
end 

class Cat < ActiveRecord::Base 
    belongs_to :wife 
end 
  1. que puedo encontrar hombres cuyas esposas tienen gatos que son el rojo y siete años de edad

    @men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => 'DISTINCT men'}) 
    

    Y puedo incluso encontrar los hombres cuyas mujeres tienen gatos de más de 20 libras y mayores de 6 años

    @men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => 'DISTINCT men'}) 
    

    Pero eso no es así lo que quiero. Quiero encontrar a los hombres cuyas esposas tengan entre ellos al menos un gato rojo y un gato de siete años, que no tiene que ser el mismo gato, o encontrar a los hombres cuyas esposas tengan al menos un gato por encima de un peso determinado y un gato más viejo que una edad determinada.
    (en los ejemplos posteriores, por favor suponer la presencia de la apropiada joins_ y DISTINCT)

  2. puedo encontrar hombres con esposas llamada Esther

    @men = Man.has_wife_named('Esther') 
    

    Incluso puedo encontrar hombres con esposas llamada Esther, Ruth O Ada (dulce!)

    @men = Man.has_wife_named(['Esther', 'Ruth', 'Ada']) 
    

    pero quiero encontrar hombres con esposas llamada Esther, Ruth y Ada.

  3. Ja, ja, solamente en broma, en realidad, necesito esto: no puedo encontrar hombres con esposas menores de 30 años llamada Esther

    @men = Man.has_young_wife_named('Esther') 
    

    encontrar a los hombres con las mujeres jóvenes llamada Ester, Rut o Ada

    @men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada']) 
    

    pero como arriba quiero encontrar hombres con mujeres jóvenes llamadas Esther AND Ruth AND Ada. Afortunadamente, el mínimo se soluciona en este caso, pero sería bueno especificar también una edad mínima.

  4. hay una manera para probar una desigualdad con una sintaxis de hash, o qué siempre tienen que volver a :conditions => ["", n] - tenga en cuenta la diferencia entre has_young_wife_named y has_yw_named_v2 - Me gusta el primero mejor, pero el rango sólo funciona para finita valores. Si está buscando una esposa vieja, creo que podría usar a..100, pero luego, cuando una esposa cumpla 101 años, abandonará la búsqueda. (hmm.¿Ella puede cocinar? j/k)

  5. ¿hay alguna manera de utilizar un alcance dentro de un alcance? Me encantaría que :has_red_cat pudiera usar :has_cat_of_color de alguna manera, o si hubiera alguna forma de usar el alcance desde un registro secundario en su padre, para poder poner los ámbitos relacionados con el gato en el modelo Wife.

Realmente no quiero hacer esto en SQL sin necesidad de aplicar named_scope, a menos que haya algo más en realidad más bonito - sugerencias para plugins y otras cosas muy apreciadas, o dirección en el tipo de SQL que necesitaré aprender. Un amigo sugirió que las UNIONES o sub-búsquedas funcionarían aquí, pero esas no parecen ser discutidas mucho en el contexto de Rails. Todavía no sé nada sobre vistas, ¿serían útiles? ¿Hay una manera feliz de hacerlos?

¡Gracias!

Como yo iba a St. Ives
me encontré con un hombre con siete esposas
Cada mujer tenía siete capturas
Cada saco tenía siete gatos
Cada gato tenía siete kits
Kits, gatos, sacos, esposas
¿Cuántos iban a St Ives?

+1

Es una pregunta realmente interesante en abstracto. Y entiendo que los nombres de su modelo y el alcance se derivan de una canción de cuna. Pero es ofensivo, para mí. Esposa: pertenece_ a Hombre? ¿Seriamente? –

+0

Sí, pensé en eso cuando lo estaba escribiendo, pero el poema se me vino a la mente y no pude deshacerme de él. –

+0

Me gustaría encontrar una buena manera de hacer 'UNION's en ámbitos con nombre en AR. Sé que puedes hacerlo en SQLAlchemy. – mikelikespie

Respuesta

2

Bueno, he tenido grandes resultados con named_scope s como éstas:

named_scope :has_cat_older_than lambda { |a| { :conditions => ["men.id in (select man_id from wives where wives.id in (select wife_id from cats where age > ?))", a] } } 

y

named_scope :has_young_wife_named lambda { |n| { :conditions => ["men.id in (select man_id from wives where name = ? and age < 30)", n] } } 

ahora puedo hacer con éxito

Member.has_cat_older_than(6).has_young_wife_named('Miriam').has_young_wife_named('Vashti') 

y conseguir lo que' Estoy esperando. Estos ámbitos no requieren el uso de las uniones, y parecen jugar bien con las otras combinaciones de estilo.

w00t!

Comentario provocado sobre si esta es una forma eficiente de hacer esto, o si hay una manera más de 'rails-y'. Una forma de incluir un ámbito de otro modelo como un fragmento de subconsulta sql podría ser útil ...

0

Usó la solución más original para Rails. Straight SQL tendrá el mismo rendimiento, por lo que no hay ninguna razón para usarlo.

+0

No estoy buscando más rendimiento. Estoy buscando que la consulta funcione correctamente. Me gustaría saber cómo estructurar correctamente mi consulta dentro de Rails, o cómo escribir SQL que lo hará. –

2

He usado construct_finder_sql para lograr la subselección de un named_scope dentro de otro. Puede no serlo para todos, pero al usarlo, permítanos SECAR un par de métodos que utilizamos para los informes.

Man.has_cat_older_than(6).send(:construct_finder_sql,{}) 

Probar eso en su secuencia de comandos/consola.

+0

Me tomó 7 meses entender esto finalmente, y es realmente muy útil. Creo que resolvería mi problema original, y simplemente desató un problema similarmente complicado. Pro-tip: 'send (: construct_finder_sql, {: seleccione => 'men.id'}))' para activar un ámbito en una cadena de modo que se excluya registros con, por ejemplo: ' named_scope: excluding_sql, lambda {| sql | {: conditions => "men.id NOT IN (# {sql})"}} ' como este: ' Man.complex_scope_one.exclude_sql (Man.complex_scope_two.send (: construct_finder_sql, {: select => 'men .id '}))) ' esto excluirá complex_scope_two from complex_scope_one ¡Gracias! –

Cuestiones relacionadas