85 votes

Sous-requêtes dans l'activeecord

Avec SQL, je peux facilement faire des sous-requêtes comme ceci

User.where(:id => Account.where(..).select(:user_id))

Cela produit :

SELECT * FROM users WHERE id IN (SELECT user_id FROM accounts WHERE ..)

Comment puis-je faire cela en utilisant les 3 activerecord/ arel/ meta_where de rails ?

J'ai besoin/veux de vraies sous-requêtes, pas de contournement par ruby (en utilisant plusieurs requêtes).

131voto

Rails le fait maintenant par défaut :)

Message.where(user_id: Profile.select("user_id").where(gender: 'm'))

produira le SQL suivant

SELECT "messages".* FROM "messages" WHERE "messages"."user_id" IN (SELECT user_id FROM "profiles" WHERE "profiles"."gender" = 'm')

(le numéro de version auquel "maintenant" fait référence est très probablement la 3.2)

6 votes

Comment faire de même si la condition est NOT IN ?

13 votes

@coorasse : Si vous utilisez Rails 4, il y a maintenant une not condition . J'ai pu l'accomplir dans Rails 3 en ajustant l'approche dans ce poste : subquery = Profile.select("user_id").where(gender: 'm')).to_sql; Message.where('user_id NOT IN (#{subquery})) En gros, ActiveRecord sont utilisées pour créer la sous-requête complète et correctement citée, qui est ensuite intégrée dans la requête externe. Le principal inconvénient est que les paramètres de la sous-requête ne sont pas liés.

4 votes

Juste pour terminer le point de @twelve17 sur Rails 4, la syntaxe spécifique not est Message.where.not(user_id: Profile.select("user_id").where(gender: 'm')) - qui génère une sous-sélection "NOT IN". Cela a résolu mon problème

43voto

Scott Lowe Points 9412

Dans ARel, le where() Les méthodes peuvent prendre des tableaux comme arguments pour générer une requête "WHERE id IN...". Ce que vous avez écrit va donc dans le bon sens.

Par exemple, le code ARel suivant :

User.where(:id => Order.where(:user_id => 5)).to_sql

... ce qui équivaut à :

User.where(:id => [5, 1, 2, 3]).to_sql

... produirait le SQL suivant sur une base de données PostgreSQL :

SELECT "users".* FROM "users" WHERE "users"."id" IN (5, 1, 2, 3)" 

Mise à jour : en réponse aux commentaires

Ok, j'ai mal compris la question. Je crois que vous voulez que la sous-requête énumère explicitement les noms des colonnes qui doivent être sélectionnées afin d'éviter de soumettre la base de données à deux requêtes (ce que fait ActiveRecord dans le cas le plus simple).

Vous pouvez utiliser project pour le select dans votre sous-sélection :

accounts = Account.arel_table
User.where(:id => accounts.project(:user_id).where(accounts[:user_id].not_eq(6)))

... ce qui produirait le SQL suivant :

SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT user_id FROM "accounts" WHERE "accounts"."user_id" != 6)

J'espère sincèrement que je vous ai donné ce que vous vouliez cette fois-ci !

0 votes

Oui, mais c'est exactement ce que je fais pas car il génère deux requêtes distinctes et non une seule contenant une sous-requête.

0 votes

Je m'excuse d'avoir mal compris votre question. Pourriez-vous donner un exemple de ce à quoi vous voulez que votre SQL ressemble ?

0 votes

Aucun problème. C'est déjà mentionné ci-dessus : SELECT * FROM users WHERE id IN (SELECT user_id FROM accounts WHERE ..)

24voto

John Points 4635

Je cherchais moi-même la réponse à cette question, et j'ai trouvé une autre approche. J'ai pensé que je devais la partager - j'espère que cela aidera quelqu'un ! :)

# 1. Build you subquery with AREL.
subquery = Account.where(...).select(:id)
# 2. Use the AREL object in your query by converting it into a SQL string
query = User.where("users.account_id IN (#{subquery.to_sql})")

Bingo ! Bango !

Fonctionne avec Rails 3.1

4 votes

Il exécute la première requête deux fois. il est préférable de faire subquery = Account.where(...).select(:id).to_sql query = User.where("users.account_id IN (#{subquery})")

10 votes

La première requête ne sera exécutée que deux fois dans votre REPL, car elle appelle to_s sur la requête pour l'afficher. Elle ne l'exécutera qu'une fois dans votre application.

0 votes

Que faire si nous voulons plusieurs colonnes à partir des tables de comptes ?

2voto

gucki Points 1416

Oui, je viens de voir que metawhere 2 (squeel) est sorti et qu'il supporte les subqueries ! !!

Pour tout savoir sur les nouveautés, cliquez ici : http://metautonomo.us/2011/04/13/introducing-squeel/

0voto

lobati Points 621

Une autre alternative :

Message.where(user: User.joins(:profile).where(profile: { gender: 'm' })

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X