2009-06-20 4 views
6

Parte del motivo per cui amo Rails è che io odio SQL - Penso che sia più simile a un linguaggio assembly che deve essere manipolato con gli strumenti di livello superiore, come ActiveRecord. Mi sembra di aver raggiunto i limiti di questo approccio, tuttavia, e sono fuori dalla mia profondità con l'SQL.Richiesta di binari complessi - unioni? sub-selezionare? posso ancora usare named_scope?

Ho un modello complesso con un sacco di sotto-record. Ho anche un set 30_40 named_scope che implementa la logica di business dal client. Questi ambiti vengono concatenati insieme in modo condizionale, motivo per cui dispongo degli ambiti joins_ in modo che i join non vengano danneggiati.

Ne ho un paio che non funzionano correttamente, o almeno non come il cliente vuole che funzionino. Ecco un'idea approssimativa della struttura del modello, con alcuni ambiti nominati (non tutti necessari per l'esempio) che illustrano il mio approccio e indicano i miei problemi. (Si prega di perdonare eventuali errori di sintassi)

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. posso trovare uomini le cui mogli hanno gatti che sono rossi e sette anni

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

    E posso nemmeno a trovare gli uomini le cui mogli hanno gatti che sono più di 20 chili e oltre 6 anni

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

    Ma non è quello che voglio. Voglio trovare gli uomini le cui mogli hanno tra loro almeno un gatto rosso e un gatto di sette anni, che non deve essere lo stesso gatto, o trovare gli uomini le cui mogli hanno tra loro almeno un gatto sopra un dato peso e un gatto più vecchio di una certa età.
    (negli esempi successivi, si prega di ipotizzare la presenza del competente joins_ e DISTINCT)

  2. posso trovare uomini con mogli di nome Esther

    @men = Man.has_wife_named('Esther') 
    

    posso anche trovare uomini con mogli di nome Esther, Ruth O Ada (dolce!)

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

    ma voglio trovare uomini con mogli di nome Esther e Ruth e Ada.

  3. Ah ah, solo scherzando, in realtà, ho bisogno di questo: riesco a trovare uomini con mogli sotto i 30 anni di nome Esther

    @men = Man.has_young_wife_named('Esther') 
    

    Trova ragazzo con i giovani mogli di nome Esther, Ruth o Ada

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

    ma come sopra voglio trovare uomini con giovani mogli di nome Esther e Ruth e Ada. Fortunatamente, in questo caso è stato fissato il minimo, ma sarebbe bello specificare anche un'età minima.

  4. c'è un modo per testare una disuguaglianza con una sintassi hash, o avete sempre ripristinare :conditions => ["", n] - notare la differenza tra has_young_wife_named e has_yw_named_v2 - mi piace la prima meglio, ma la gamma funziona solo per finita valori. Se stai cercando una vecchia moglie, immagino che potresti usare lo a..100 ma poi quando una moglie compie 101 anni lascia la ricerca. (hmm.lei può cucinare? j/k)

  5. esiste un modo per utilizzare un ambito nell'ambito di un ambito? Mi piacerebbe che se :has_red_cat fosse in grado di utilizzare :has_cat_of_color in qualche modo, o se ci fosse un modo per utilizzare l'ambito da un record figlio nel suo genitore, così avrei potuto inserire gli ambiti relativi al gatto nel modello Wife.

io davvero non voglio fare questo in SQL dritto senza usare named_scope, a meno che non ci sia qualcos'altro in realtà più bello - suggerimenti per plugin e quant'altro molto apprezzato, o la direzione in quella sorta di SQL Ho bisogno di imparare. Un amico ha suggerito che le UNION o le sotto-ricerche funzionerebbero qui, ma quelle non sembrano essere discusse molto nel contesto di Rails. Non so ancora nulla delle opinioni - sarebbero utili? C'è un modo rotaie felice di farli?

Grazie!

Mentre stavo andando a St Ives
ho incontrato un uomo con sette mogli
Ogni moglie aveva sette sacchi
Ogni sacco aveva sette gatti
Ogni gatto aveva sette kit
Kit, gatti, sacchi, mogli
Quanti stavano andando a St Ives?

+1

E 'una domanda molto interessante in astratto. E capisco che i nomi dei modelli e degli obiettivi sono derivati ​​da una filastrocca. Ma è sconvolgente, per me. Moglie: appartiene all'uomo? Sul serio? –

+0

sì, ci ho pensato quando lo stavo scrivendo, ma il poema mi era saltato in testa e non riuscivo a liberarmene. –

+0

Mi piacerebbe trovare un buon modo per fare 'UNION's in ambiti denominati in AR. So che puoi farli in SQLAlchemy. – mikelikespie

risposta

2

Beh, ho avuto grandi risultati con named_scope s come questi:

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] } } 

e

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

ora posso fare con successo

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

e ottenere quello che ho' Mi aspetto Questi ambiti non richiedono l'uso dei join e sembrano giocare bene con gli altri join in stile.

w00t!

I commenti hanno dimostrato se questo è un modo efficace per farlo o se esiste un modo più "rails-y". Un modo per includere un campo di applicazione da un altro modello come un frammento sottoquery sql potrebbe essere utile ...

0

Hai utilizzato la soluzione più nativa per Rails. SQL diretto avrà le stesse prestazioni quindi non c'è motivo di usarlo.

+0

Non sto cercando prestazioni migliori - sto cercando la query per funzionare correttamente. Mi piacerebbe anche sapere come strutturare correttamente la mia query in Rails, o come scrivere SQL che lo farà. –

2

ho usato construct_finder_sql per compiere la subselect di una named_scope all'interno di un altro. Potrebbe non essere per tutti, ma usarlo ci permette di ASCIUGARE un paio di named_scope che abbiamo usato per i report.

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

Prova che nello script/console.

+0

Mi ci sono voluti 7 mesi per capire finalmente questo, ed è davvero molto utile. Penso che risolverebbe il mio problema originale, e ha semplicemente sciolto un nodo altrettanto problematico. Pro-consiglio: 'send (: construct_finder_sql, {: selezionare => 'men.id'}))' per trasformare un ambito in una stringa in modo da escludere i record con, ad esempio: 'named_scope: excluding_sql, lambda {| sql | {: conditions => "men.id NOT IN (# {sql})"}} ' come questo: ' Man.complex_scope_one.exclude_sql (Man.complex_scope_two.send (: construct_finder_sql, {: select => 'men .id '}))) ' esclude complex_scope_two da complex_scope_one Grazie! –

Problemi correlati