activerecord의 하위 쿼리


82

SQL을 사용하면 다음과 같은 하위 쿼리를 쉽게 수행 할 수 있습니다.

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

이것은 다음을 생성합니다.

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

rails의 3 activerecord / arel / meta_where를 사용하여 어떻게 할 수 있습니까?

실제 하위 쿼리가 필요하거나 루비 해결 방법이 없습니다 (여러 쿼리 사용).

답변:


125

Rails는 이제 기본적으로이 작업을 수행합니다. :)

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

다음 SQL을 생성합니다.

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

( "현재"가 나타내는 버전 번호는 3.2 일 가능성이 높습니다.)


6
조건이 IN이 아닌 경우 어떻게하나요?
coorasse 2013

13
@coorasse : Rails 4를 사용하는 경우 이제 not조건이 있습니다. 이 게시물 의 접근 방식을 조정하여 Rails 3 에서이 작업을 수행 할 수있었습니다 . subquery = Profile.select("user_id").where(gender: 'm')).to_sql; Message.where('user_id NOT IN (#{subquery})) 기본적으로 ActiveRecord메서드를 사용하여 완성되고 적절하게 인용 된 하위 쿼리를 만든 다음 외부 쿼리에 인라인합니다. 주된 단점은 하위 쿼리 매개 변수가 바인딩되지 않는다는 것입니다.
twelve17 2013-07-23

3
Rails 4에 대한 @ twelve17의 요점을 마치기 위해 구체적인 not 구문은 Message.where.not(user_id: Profile.select("user_id").where(gender: 'm'))"NOT IN"하위 선택을 생성하는 것입니다. 방금 내 문제를 해결했습니다.
Steve Midgley 2014 년

1
@ChristopherLindblom Rails가 기본적으로 "지금"이라고 말하면 정확히 무엇을 의미합니까? Rails 3.2부터? "Rails는 기본적으로 버전 X부터이 작업을 수행합니다"라고 대답을 변경할 수 있다면 좋을 것입니다.
Jason Swett

@JasonSwett Im 미안하지만 모르겠습니다. 당신이 말했듯이 아마도 3.2 였을 것입니다. 당시의 현재 버전이고 출시 된 버전 만 실행했습니다. 앞으로의 교정 대응 방안을 생각할 것입니다. 지적 해 주셔서 감사합니다.
Christopher Lindblom

43

ARel에서 where()메서드는 "WHERE id IN ..."쿼리를 생성하는 인수로 배열을 사용할 수 있습니다. 그래서 당신이 쓴 것은 올바른 줄을 따라 있습니다.

예를 들어, 다음 ARel 코드 :

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

... 이는 다음과 같습니다.

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

... PostgreSQL 데이터베이스에 다음 SQL을 출력합니다.

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

업데이트 : 의견에 대한 응답

좋아, 그래서 나는 그 질문을 오해했다. 두 개의 쿼리 (가장 간단한 경우 ActiveRecord가 수행하는 작업)를 사용하여 데이터베이스에 도달하지 않도록 선택할 열 이름을 하위 쿼리에 명시 적으로 나열하기를 원한다고 생각합니다.

당신은 사용할 수 있습니다 project에 대한 select당신의 하위 선택 :

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

... 다음 SQL을 생성합니다.

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

이번에는 당신이 원했던 것을 진심으로 바랍니다!


예, 그러나 이것은 내가 뭘 정확히 되지 는 하나 개의 하위 쿼리를 포함하는 단일 한 두 개의 쿼리를 생성하지 때문에합니다.
gucki

질문을 오해 한 것에 대해 사과드립니다. SQL이 어떻게 보이길 원하는지 예를 들어 주실 수 있습니까?
스콧

문제 없어요. 이미 위에서 언급했습니다. SELECT * FROM users WHERE id IN (SELECT user_id FROM accounts WHERE ..)
gucki

1
아, 그래. 나는 지금 당신이 말하는 것을 이해합니다. 2 개의 쿼리가 생성된다는 의미를 알았습니다. 다행히도 문제를 해결하는 방법을 알고 있습니다! (개정 대답을 참조)
스콧

23

나는이 질문에 대한 답을 직접 찾고 있었고 대안적인 접근법을 생각해 냈습니다. 나는 그것을 공유 할 것이라고 생각했습니다. 누군가에게 도움이되기를 바랍니다! :)

# 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})")

빙고! 뱅고!

Rails 3.1에서 작동


4
첫 번째 쿼리를 두 번 실행합니다. 하는 것이 더 낫습니다 subquery = Account.where(...).select(:id).to_sql query = User.where("users.account_id IN (#{subquery})")
coorasse 2013-02-23

9
쿼리에서 to_s를 호출하여 표시하기 때문에 REPL에서 첫 번째 쿼리를 두 번만 실행합니다. 응용 프로그램에서 한 번만 실행됩니다.
Ritchie

계정 테이블에서 여러 열을 원하면 어떻게합니까?
Ahmad hamza

0

또 다른 대안 :

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

0

다음은 레일 ActiveRecord를 사용하고 JOIN을 사용하는 중첩 하위 쿼리의 예입니다. 여기에서 각 쿼리에 대한 절과 결과를 추가 할 수 있습니다.

모델 파일에 중첩 된 inner_query 및 outer_query 범위를 추가하고 ...

  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("...")

범위의 예 :

   scope :inner_query , -> (ids) {
    select("...")
    .joins("left join users on users.id = accounts.id")
    .where("users.account_id IN (?)", ids)
    .group("...")
   }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.