2010-11-21 18 views
7

J'ai besoin de supprimer un grand nombre d'enregistrements à la fois et je dois le faire en fonction d'une condition dans un autre modèle qui est liée par une relation "belongs_to". Je sais que je peux faire une boucle à travers chaque vérification de la condition, mais cela prend une éternité avec mon grand ensemble d'enregistrements parce que pour chaque "belongs_to" il fait une requête séparée.Est-il possible de supprimer toutes les conditions de jointure interne?

Voici un exemple. J'ai un modèle "Product" qui "appartient" à un "Artist" et laisse dire que l'artiste a une propriété "is_disabled".

Si je veux supprimer tous les produits qui appartiennent à des artistes handicapés, je voudrais pouvoir faire quelque chose comme:

Product.delete_all(:joins => :artist, :conditions => ["artists.is_disabled = ?", true]) 

Est-ce possible? Je l'ai fait directement dans SQL avant, mais je ne sais pas si c'est possible de faire des rails.

Répondre

4

Le problème est que delete_all rejette toutes les informations de jointure (et à juste titre). Ce que vous voulez faire est de capturer cela comme un choix interne.

Si vous utilisez Rails 3, vous pouvez créer un champ qui vous donnera ce que vous voulez:

class Product < ActiveRecord::Base 
    scope :with_disabled_artist, lambda { 
    where("product_id IN (#{select("product_id").joins(:artist).where("artist.is_disabled = TRUE").to_sql})") 
    } 
end 

Vous appelez la requête devient alors

Product.with_disabled_artist.delete_all 

Vous pouvez également utiliser la même requête en ligne, mais ce n'est pas très élégant (ou auto-documentant):

Product.where("product_id IN (#{Product.select("product_id").joins(:artist).where("artist.is_disabled = TRUE").to_sql})").delete_all 
+2

Comment est-il "à juste titre" pour supprimer les jointures ? Des conditions de jointure sont souvent nécessaires pour limiter les enregistrements à supprimer. Je pense que le pire est qu'il le fait en silence, ce qui pourrait être assez surprenant. –

+0

Je veux juste dire que vous ne pouvez pas faire une jointure supprimer tout comme ça dans SQL de toute façon, d'où le bon. Je suis à peu près sûr que les versions ultérieures de Rails font des sous-requêtes de toute façon. Pas sûr que ce soit la meilleure approche car cela fait apparaître la requête sous-jacente dans une jointure quand c'est vraiment une sous-requête. – gcastro

+1

@gcastro Cela est faux - dans mysql au moins on peut absolument effectuer une suppression avec une déclaration de jointure. http://dev.mysql.com/doc/refman/5.7/fr/delete.html – bluefear

0

Si vous utilisez Rails 2 y Vous ne pouvez pas faire ce qui précède. Une alternative consiste à utiliser une clause joins dans une méthode find et à appeler delete sur chaque élément.

TellerLocationWidget.find(:all, :joins => [:widget, :teller_location], 
     :conditions => {:widgets => {:alt_id => params['alt_id']}, 
     :retailer_locations => {:id => @teller_location.id}}).each do |loc| 
     loc.delete 
    end 
+0

Cela entraînera des suppressions individuelles qui ne sont pas les mêmes et ne seront pas mises à l'échelle. Vous devriez être capable d'utiliser des portées nommées dans Rails 2. – gcastro

1

Dans Rails 4 (J'ai testé sur 4,2), vous pouvez faire presque comment OP voulait à l'origine

Application.joins(:vacancy).where(vacancies: {status: 'draft'}).delete_all 

donnera

DELETE FROM `applications` WHERE `applications`.`id` IN (SELECT id FROM (SELECT `applications`.`id` FROM `applications` INNER JOIN `vacancies` ON `vacancies`.`id` = `applications`.`vacancy_id` WHERE `vacancies`.`status` = 'draft') __active_record_temp) 
+0

Bien que ce soit correct sur PostgreSQL, il est mortellement lent sur le serveur MySQL. Il faut se méfier. – lzap