Activerecord'daki alt sorgular


81

SQL ile bunun gibi alt sorguları kolayca yapabilirim

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

Bu şunları üretir:

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

Bunu rails'in 3 activerecord / arel / meta_where kullanarak nasıl yapabilirim?

Gerçek alt sorgulara ihtiyacım var / istiyorum, Ruby geçici çözümleri yok (birkaç sorgu kullanarak).

Yanıtlar:


126

Rails artık bunu varsayılan olarak yapıyor :)

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

aşağıdaki SQL'i üretecek

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

("şimdi" nin atıfta bulunduğu sürüm numarası büyük olasılıkla 3.2'dir)


6
Durum IN DEĞİLSE aynısı nasıl yapılır?
coorasse

13
@coorasse: Rails 4 kullanıyorsanız, artık bir notkoşul vardır . Ben yaklaşım ayarlayarak Rails 3 bunu başarmak başardı bu yazı : subquery = Profile.select("user_id").where(gender: 'm')).to_sql; Message.where('user_id NOT IN (#{subquery})) Temel olarak, ActiveRecordyöntemler daha sonra dış sorguya satır içi tamamlanan düzgün alıntılanan alt sorgu oluşturmak için kullanılır. Ana dezavantajı, alt sorgu parametrelerinin bağlı olmamasıdır.
oniki17

3
@ Twelve17'nin Rails 4 ile ilgili noktasını bitirmek için, spesifik olmayan sözdizimi Message.where.not(user_id: Profile.select("user_id").where(gender: 'm'))- bu bir "NOT IN" alt seçimi oluşturur. Sadece sorunumu
çözdüm

1
@ChristopherLindblom Rails "şimdi" dediğinizde bunu varsayılan olarak yapıyor, tam olarak ne demek istiyorsunuz? Rails 3.2'den itibaren? Cevabı değiştirip "Rails bunu X sürümünden itibaren varsayılan olarak yapar" şeklinde değiştirebilseydik iyi olurdu.
Jason Swett

@JasonSwett Üzgünüm bilmiyorum, sizin de söylediğiniz gibi muhtemelen 3.2 idi, çünkü zamanın güncel sürümü ve sadece yayınlanan sürümleri çalıştırıyordu. İleriye dönük gelecek prova cevapları hakkında düşünecek, buna işaret ettiğiniz için teşekkür ederiz.
Christopher Lindblom

43

ARel'de where()yöntemler, dizileri "WHERE id IN ..." sorgusu oluşturacak argüman olarak alabilir. Yani yazdıklarınız doğru satırlardadır.

Örneğin, aşağıdaki ARel kodu:

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

... eşdeğer olan:

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

... bir PostgreSQL veritabanında aşağıdaki SQL'i çıktılar:

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

Güncelleme: yorumlara yanıt olarak

Tamam, yani soruyu yanlış anladım. Veritabanını iki sorgu ile vurmamak için alt sorgunun seçilecek sütun adlarını açıkça listelemesini istediğinizi düşünüyorum (en basit durumda ActiveRecord bunu yapar).

Alt seçiminizdeki projectiçin kullanabilirsiniz select:

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

... aşağıdaki SQL'i üretir:

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

İçtenlikle umarım bu sefer size istediğinizi vermişimdir!


Evet, ama tam olarak istemediğim şey bu çünkü iki ayrı sorgu oluşturuyor ve bir alt sorgu içeren tek bir sorgu oluşturmuyor.
gucki

Sorunuzu yanlış anladığınız için özür dileriz. SQL'inizin nasıl görünmesini istediğinize bir örnek verebilir misiniz?
Scott

Sorun değil. Yukarıda zaten bahsedilmişti: SEÇİN * KULLANICILARIN NEREDE
GİRDİĞİNDEN (

1
Ah tamam. Şimdi ne dediğini anlıyorum. Oluşturulan 2 sorgu hakkında ne demek istediğini anlıyorum. Neyse ki, sorununuzu nasıl çözeceğimi biliyorum! (gözden geçirilmiş cevaba bakın)
Scott

23

Bu sorunun cevabını kendim arıyordum ve alternatif bir yaklaşım buldum. Sadece paylaşacağımı düşündüm - umarım birine yardımcı olur! :)

# 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!

Rails 3.1 ile çalışır


4
ilk sorguyu iki kez yürütür. yapmak daha iyi subquery = Account.where(...).select(:id).to_sql query = User.where("users.account_id IN (#{subquery})")
coorasse

10
REPL'inizde ilk sorguyu yalnızca iki kez yürütür çünkü onu görüntülemek için sorguda to_s çağırır. Bunu uygulamanızda yalnızca bir kez çalıştırır.
Ritchie

Hesap tablolarından birden çok sütun istiyorsak ne olur?
Ahmad hamza

0

Başka bir alternatif:

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

0

Bu, ActiveRecord raylarını kullanan ve JOIN'leri kullanan iç içe geçmiş bir alt sorgunun bir örneğidir; burada her sorguya ve sonuca cümle ekleyebilirsin:

Model dosyanıza yuvalanmış iç_sorgu ve dış_sorgu kapsamlarını ekleyebilir ve ...

  inner_query = Account.inner_query(params)
  result = User.outer_query(params).joins("(#{inner_query.to_sql}) alias ON users.id=accounts.id")
   .group("alias.grouping_var, alias.grouping_var2 ...")
   .order("...")

Kapsamın bir örneği:

   scope :inner_query , -> (ids) {
    select("...")
    .joins("left join users on users.id = accounts.id")
    .where("users.account_id IN (?)", ids)
    .group("...")
   }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.