2011-09-15 10 views
5

che sto ricevendo query SQL molto brutto con Rails codice come uno di seguito elencati:ridurre l'uso di LEFT JOIN nelle query con includono

Facility.includes(:type, :owner_building, :delegated_building, keeper_building, :owner_user, :keeper_user).order('users.name ASC').all 

Produce:

SELECT `facilities`.`id` AS t0_r0, `facilities`.`name` AS t0_r1, `facilities`.`brand` AS t0_r2, `facilities`.`desc` AS t0_r3, `facilities`.`type_id` AS t0_r4, `facilities`.`owner_building_id` AS t0_r5, `facilities`.`keeper_building_id` AS t0_r6, `facilities`.`delegated_building_id` AS t0_r7, `facilities`.`owner_user_id` AS t0_r8, `facilities`.`keeper_user_id` AS t0_r9, `buildings`.`id` AS t1_r0, `buildings`.`name` AS t1_r1, `buildings`.`address` AS t1_r2, `buildings`.`created_at` AS t1_r3, `buildings`.`updated_at` AS t1_r4, `buildings`.`comments` AS t1_r5, `delegated_buildings_facilities`.`id` AS t2_r0, `delegated_buildings_facilities`.`name` AS t2_r1, `delegated_buildings_facilities`.`address` AS t2_r2, `delegated_buildings_facilities`.`created_at` AS t2_r3, `delegated_buildings_facilities`.`updated_at` AS t2_r4, `delegated_buildings_facilities`.`comments` AS t2_r5, `keeper_buildings_facilities`.`id` AS t3_r0, `keeper_buildings_facilities`.`name` AS t3_r1, `keeper_buildings_facilities`.`address` AS t3_r2, `keeper_buildings_facilities`.`created_at` AS t3_r3, `keeper_buildings_facilities`.`updated_at` AS t3_r4, `keeper_buildings_facilities`.`comments` AS t3_r5, `users`.`id` AS t4_r0, `users`.`company_id` AS t4_r1, `users`.`building_id` AS t4_r2, `users`.`login` AS t4_r3, `users`.`name` AS t4_r4, `users`.`role` AS t4_r5, `users`.`email` AS t4_r6, `users`.`comments` AS t4_r7, `users`.`crypted_password` AS t4_r8, `users`.`password_salt` AS t4_r9, `users`.`persistence_token` AS t4_r10, `users`.`perishable_token` AS t4_r11, `users`.`login_count` AS t4_r12, `users`.`failed_login_count` AS t4_r13, `users`.`last_request_at` AS t4_r14, `users`.`current_login_at` AS t4_r15, `users`.`last_login_at` AS t4_r16, `users`.`current_login_ip` AS t4_r17, `users`.`last_login_ip` AS t4_r18, `users`.`created_at` AS t4_r19, `users`.`updated_at` AS t4_r20, `keeper_users_facilities`.`id` AS t5_r0, `keeper_users_facilities`.`company_id` AS t5_r1, `keeper_users_facilities`.`building_id` AS t5_r2, `keeper_users_facilities`.`login` AS t5_r3, `keeper_users_facilities`.`name` AS t5_r4, `keeper_users_facilities`.`role` AS t5_r5, `keeper_users_facilities`.`email` AS t5_r6, `keeper_users_facilities`.`comments` AS t5_r7, `keeper_users_facilities`.`crypted_password` AS t5_r8, `keeper_users_facilities`.`password_salt` AS t5_r9, `keeper_users_facilities`.`persistence_token` AS t5_r10, `keeper_users_facilities`.`perishable_token` AS t5_r11, `keeper_users_facilities`.`login_count` AS t5_r12, `keeper_users_facilities`.`failed_login_count` AS t5_r13, `keeper_users_facilities`.`last_request_at` AS t5_r14, `keeper_users_facilities`.`current_login_at` AS t5_r15, `keeper_users_facilities`.`last_login_at` AS t5_r16, `keeper_users_facilities`.`current_login_ip` AS t5_r17, `keeper_users_facilities`.`last_login_ip` AS t5_r18, `keeper_users_facilities`.`created_at` AS t5_r19, `keeper_users_facilities`.`updated_at` AS t5_r20, `facility_types`.`id` AS t6_r0, `facility_types`.`name` AS t6_r1, `facility_types`.`desc` AS t6_r2, `facility_migrations`.`id` AS t7_r0, `facility_migrations`.`building_id` AS t7_r1, `facility_migrations`.`equipment_id` AS t7_r2, `facility_migrations`.`facility_id` AS t7_r3, `facility_migrations`.`created_at` AS t7_r4 
FROM `facilities` 
LEFT OUTER JOIN`buildings` ON `buildings`.`id` = `facilities`.`owner_building_id` 
LEFT OUTER JOIN`buildings` `delegated_buildings_facilities` ON `delegated_buildings_facilities`.`id` = `facilities`.`delegated_building_id` 
LEFT OUTER JOIN`buildings` `keeper_buildings_facilities` ON `keeper_buildings_facilities`.`id` = `facilities`.`keeper_building_id` 
LEFT OUTER JOIN`users` ON `users`.`id` = `facilities`.`owner_user_id` 
LEFT OUTER JOIN`users` `keeper_users_facilities` ON `keeper_users_facilities`.`id` = `facilities`.`keeper_user_id` 
LEFT OUTER JOIN`facility_types` ON `facility_types`.`id` = `facilities`.`type_id` 
LEFT OUTER JOIN`facility_migrations` ON `facility_migrations`.`facility_id` = `facilities`.`id` 
WHERE `facilities`.`id` IN (15, 47, 16, 48, 17, 49, 18, 50, 19, 51, 20, 52) AND ((1=1)) ORDER BY users.name ASC 

Così come posso usare LEFT JOIN solo per i campi in cui sono presenti condizioni (come l'ordinamento) e semplici SELECT per altre tabelle (come include regolarmente il lavoro quando non ci sono condizioni)?

+0

Non ho molta familiarità con Rails ma cosa fa '.all' alla fine del codice? –

+0

Trova tutto - In questo modo verranno restituiti tutti i record corrispondenti alle opzioni utilizzate. Da qui: http://apidock.com/rails/ActiveRecord/FinderMethods/find – sunki

+0

Penso che dovresti usare '.joins()' invece di '.includes()' per le tabelle che vuoi 'INNER JOIN' . Vedi questa domanda SO: http://stackoverflow.com/questions/4861416/whats-the-difference-between-includes-and-joins-in-activerecord-query e la guida: http://guides.rubyonrails.org/ active_record_querying.html # join-tables –

risposta

0

Come nessuno sembra averlo ancora menzionato. C'è un metodo chiamato preload che fa quasi lo stesso di includes, tranne che utilizza query separate anziché join di sinistra.

Se si desidera che i join esterni a sinistra siano ordinati/raggruppati/filtrati e non inclusi nei risultati, è possibile utilizzare lo squeel gem invece, supporta i join esterni nel metodo joins.

2

Non capisco completamente la domanda; Ma intendi per Ugly "Seleziona A come B, X come Y"?

In tal caso, questo è il modo Rails (AR) di mappare correttamente i valori restituiti ai rispettivi oggetti corrispondenti. Qualsiasi t0_r * verrà mappato su un oggetto facility, qualsiasi t1_r * verrà mappato su un oggetto di costruzione, ecc ...

L'utilizzo di select non ti aiuterà.

Questa query viene utilizzata da Rails internamente e, per quanto ricordo, altri ORM fanno lo stesso (Hibernate per esempio).

+1

Corretto, Ibernazione (o NHibernate) fa qualcosa di molto simile. – yfeldblum

+0

Non proprio così. Solitamente AR non crea JOIN se non ci sono condizioni sui campi delle tabelle associate (utilizza SELECT separati per caricare le associazioni). Quello che voglio è usare JOIN una volta su 'users' table e SELECTs per altre associazioni. – sunki

+1

Hai ragione riguardo alle singole istruzioni utilizzate per caricare gli oggetti associati. Ma quando usi l'ordine (...) 'stai ordinando' Facility' dagli utenti. Se sono 2 selezioni indipendenti - si finirà con un 'utente' ordinato - quindi un elenco di strutture (non ordinate) che ha utenti associati dalla prima query. – tamersalama

2

Purtroppo non è possibile combinare join (: associazioni) (o selezionare, gruppo ecc.) Con lo strumento di caricamento desideroso "include (: associazioni)". Quindi una soluzione sarebbe quella di tagliare la tua query in 2 che è sempre meglio dell'effetto O (n) del caricamento pigro o del caricamento di molti oggetti activerecord indesiderati (molto costosi):

Prima ottenere gli ID di impianto filtrati senza il associazioni non c'è bisogno di caricare

facilities_ids = Facility.select("facilities.id"). 
        joins([:conditional_associations, ...]).map(&:id) 

poi usarli per filtrare la query ansiosi con tutta la bontà del "comprende":

@facilities = Facility.includes([:loaded_associations]). 
       where(["facilities.id IN (?)", facilities_ids]) 
+0

È un po 'che uso ora. Ma ho pensato che esiste un modo più chiaro. Grazie comunque! – sunki

+0

Un'altra soluzione implica che non instradiate le associazioni ma piuttosto le caricate come attributi alias (che è piuttosto noioso). Inoltre, con l'aiuto dell'eccellente squeel di gemme, è possibile combinare le giunture esterne interne. saresti interessato ? – charlysisto

0

Questa è una domanda modello di dati, non è una questione AR . AR sta solo esponendo possibili problemi di progettazione dello schema.

Personalmente, vorrei indagare perché deligated_builds sono in una tabella diversa da edifici. Dovrebbero probabilmente trovarsi nella stessa tabella, quindi è possibile specificare quale ID di edificio delegato solo in una clausola di query anziché in join. Lo stesso vale per tabelle _building simili.

+0

Gli edifici delegati si trovano nella tabella degli edifici. Perché hai deciso che non è così? – sunki

+0

Perchè ti unisci ad edifici come 4 volte? a causa di STI? –