레일 : 포함 대 : 조인


345

이것은 "어떻게해야하는지 모르겠다"라는 질문이 아니라 "왜 이런 식으로 작동 하는가"라는 질문입니다.

따라서 사용하려는 관련 레코드를 :include가져 오는 데 대한 복음 은 참여를 얻고 추가 ​​쿼리를 많이하지 않기 때문에 사용 하는 것입니다.

Post.all(:include => :comments)

그러나 로그를 볼 때 조인이 발생하지 않습니다.

Post Load (3.7ms)   SELECT * FROM "posts"
Comment Load (0.2ms)   SELECT "comments.*" FROM "comments" 
                       WHERE ("comments".post_id IN (1,2,3,4)) 
                       ORDER BY created_at asc) 

그것은 있다 가 한 번에 의견을 모두 끌어 때문에 바로 가기를 복용하지만 조인 아직 아니다 (인 모든 문서는 말을 보이는). 조인을 얻을 수있는 유일한 방법 :joins:include다음 대신 사용하는 것입니다 .

Post.all(:joins => :comments)

그리고 로그는 다음과 같이 표시됩니다.

Post Load (6.0ms)  SELECT "posts".* FROM "posts" 
                   INNER JOIN "comments" ON "posts".id = "comments".post_id

뭔가 빠졌습니까? 십여 개의 연결이있는 앱이 있고 한 화면에 모든 데이터를 표시합니다. 6 명의 개인 대신 하나의 결합 된 쿼리를 갖는 것이 더 나은 것 같습니다. 성능 측면에서 개별 쿼리보다는 조인을 수행하는 것이 항상 낫지는 않다는 것을 알고 있습니다 (사실 시간이 지남에 따라 위의 두 개별 쿼리가 조인보다 빠르다는 것처럼 보입니다). 그러나 모든 문서 후에 나는 읽은 :include대로 광고가 작동하지 않는 것을보고 놀랐습니다 .

Rails 성능 문제를 인식하고 특정 경우를 제외하고는 참여하지 않습니까?


3
이전 버전의 Rails를 사용하는 경우 태그를 통해 또는 질문 본문에 명시하십시오. 그렇지 않으면, 지금 Rails 4를 사용하고 있다면, includes(이 글을 읽는 사람에게는)
onebree

또한 : preload 및 : eager_load blog.bigbinary.com/2013/07/01/…가 있습니다
CJW

답변:


179

:include기능은 Rails 2.1에서 변경된 것으로 보입니다 . 레일은 모든 경우에 조인을 수행했지만 성능상의 이유로 일부 상황에서 여러 쿼리를 사용하도록 변경되었습니다. Fabio Akita 의이 블로그 게시물 에는 변경 사항에 대한 유용한 정보가 있습니다 ( "최적화 된 Eager 로딩"섹션 참조).



이것은 매우 도움이됩니다. Rails가 필요로하는 'where'가 없어도 Rails가 조인을 수행 할 수있는 방법이 있었으면 좋겠습니다. 경우에 따라 조인의 효율성이 높아지고 복제 위험이 발생하지 않습니다.
조나단 스와 츠


@JonathanSwartz 새 버전 레일이 eagerload를 사용하여 이를 지원하는 것 같습니다 . 링크 NathanLong
rubyprince 7:12의

92

.joins테이블을 조인하고 선택된 필드를 반환합니다. 조인 쿼리 결과에 대한 연결을 호출하면 데이터베이스 쿼리가 다시 시작됩니다.

:includes포함 된 연결을 열망하여 메모리에 추가합니다. :includes포함 된 모든 테이블 속성을로드합니다. 포함 쿼리 결과에 대한 연결을 호출하면 쿼리가 실행되지 않습니다.


71

조인과 include의 차이점은 include 문을 사용하면 다른 테이블의 모든 속성을 메모리에로드하는 훨씬 더 큰 SQL 쿼리를 생성한다는 것입니다.

예를 들어, 주석으로 가득 찬 테이블이 있고 : joins => users를 사용하여 정렬 목적으로 모든 사용자 정보를 가져 오는 경우 등이 잘 작동하고 : include보다 시간이 덜 걸리지 만 표시하고 싶다고 말하십시오. : joins를 사용하여 정보를 얻으려면 가져온 각 사용자에 대해 별도의 SQL 쿼리를 작성해야하지만 : include를 사용하면이 정보를 사용할 수 있습니다.

좋은 예 :

http://railscasts.com/episodes/181-include-vs-joins


55

나는 최근의 차이에 더 읽고 있었다 :joins:includes레일한다. 여기에 내가 이해 한 것에 대한 설명이 있습니다 (예 :))

이 시나리오를 고려하십시오.

  • 사용자는 사용자에게 많은 댓글과 댓글이 있습니다.

  • 사용자 모델에는 Name (string), Age (integer) 속성이 있습니다. 주석 모델에는 Content, user_id 속성이 있습니다. 주석의 경우 user_id는 null 일 수 있습니다.

조인 :

: joins 는 두 테이블간에 내부 조인을 수행합니다 . 그러므로

Comment.joins(:user)

#=> <ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first   comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">, 
     #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,    
     #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">]>

주석 테이블의 user_id가 user.id (users 테이블)와 같은 모든 레코드 를 가져옵니다 . 따라서 당신이 할 경우

Comment.joins(:user).where("comments.user_id is null")

#=> <ActiveRecord::Relation []>

그림과 같이 빈 배열이 나타납니다.

또한 조인은 조인 된 테이블을 메모리에로드하지 않습니다. 따라서 당신이 할 경우

comment_1 = Comment.joins(:user).first

comment_1.user.age
#=>←[1m←[36mUser Load (0.0ms)←[0m  ←[1mSELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1←[0m  [["id", 1]]
#=> 24

보시다시피 comment_1.user.age백그라운드에서 데이터베이스 쿼리를 다시 시작하여 결과를 얻습니다.

포함 사항 :

: includes 는 두 테이블간에 왼쪽 외부 조인을 수행합니다 . 그러므로

Comment.includes(:user)

#=><ActiveRecord::Relation [#<Comment id: 1, content: "Hi I am Aaditi.This is my first comment!", user_id: 1, created_at: "2014-11-12 18:29:24", updated_at: "2014-11-12 18:29:24">,
   #<Comment id: 2, content: "Hi I am Ankita.This is my first comment!", user_id: 2, created_at: "2014-11-12 18:29:29", updated_at: "2014-11-12 18:29:29">,
   #<Comment id: 3, content: "Hi I am John.This is my first comment!", user_id: 3, created_at: "2014-11-12 18:30:25", updated_at: "2014-11-12 18:30:25">,    
   #<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

주석 테이블의 모든 레코드있는 조인 된 테이블이됩니다. 따라서 당신이 할 경우

Comment.includes(:user).where("comment.user_id is null")
#=> #<ActiveRecord::Relation [#<Comment id: 4, content: "Hi This is an anonymous comment!", user_id: nil, created_at: "2014-11-12 18:31:02", updated_at: "2014-11-12 18:31:02">]>

comment.user_id가 nil 인 레코드를 표시된대로 가져옵니다.

또한 메모리에 두 테이블을 모두로드합니다. 따라서 당신이 할 경우

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

알 수 있듯이 comment_1.user.age는 백그라운드에서 데이터베이스 쿼리를 실행하지 않고 단순히 메모리에서 결과를로드합니다.


이것이 Rails 4를위한 것입니까?
onebree

@HunterStevens : 그렇습니다
Aaditi Jain

54

성능 고려 사항 외에도 기능상의 차이도 있습니다. 댓글에 참여하면 댓글이있는 게시물 (기본적으로 내부 가입)이 필요합니다. 의견을 포함 시키면 모든 게시물 (외부 조인)을 요청하는 것입니다.


10

tl; dr

나는 두 가지 방식으로 대조합니다.

조인 -조건부로 레코드를 선택합니다.

포함 -결과 세트의 각 멤버에 연관을 사용할 때.

더 긴 버전

조인은 데이터베이스에서 나오는 결과 집합을 필터링하기위한 것입니다. 이를 사용하여 테이블에서 작업을 설정합니다. 이것을 집합 이론을 수행하는 where 절로 생각하십시오.

Post.joins(:comments)

와 같다

Post.where('id in (select post_id from comments)')

한 개 이상의 의견이있는 경우를 제외하고는 조인으로 중복 된 게시물을 다시 받게됩니다. 그러나 모든 게시물은 댓글이있는 게시물이됩니다. 다음과 같이 구별하여 수정할 수 있습니다.

Post.joins(:comments).count
=> 10
Post.joins(:comments).distinct.count
=> 2

계약에서이 includes방법은 관계를 참조 할 때 추가 데이터베이스 쿼리가 없는지 확인하기 만합니다 (따라서 n + 1 쿼리를 만들지 않음)

Post.includes(:comments).count
=> 4 # includes posts without comments so the count might be higher.

도덕은 joins조건부 집합 작업을 수행 includes하려고 할 때 사용하고 컬렉션의 각 멤버에서 관계를 사용하려고 할 때 사용하는 것입니다.


그것은 distinct매번 나를 얻는다. 감사합니다!
벤 헐

4

.joins는 데이터베이스 조인으로 작동하며 둘 이상의 테이블을 조인하고 선택한 데이터를 백엔드 (데이터베이스)에서 가져옵니다.

.는 데이터베이스의 왼쪽 조인으로 작업을 포함합니다. 왼쪽의 모든 레코드를로드했으며 오른쪽 모델과 관련이 없습니다. 메모리에 모든 관련 개체를로드하기 때문에 열망에 사용됩니다. 포함 쿼리 결과에 대한 연결을 호출하면 데이터베이스에서 쿼리를 실행하지 않고 메모리에 이미 데이터를로드했기 때문에 메모리에서 데이터를 반환합니다.


0

'join'은 테이블을 조인하는 데 사용되었으며 조인에서 연관을 호출하면 쿼리가 다시 발생합니다 (많은 쿼리가 실행됨을 의미 함)

lets suppose you have tow model, User and Organisation
User has_many organisations
suppose you have 10 organisation for a user 
@records= User.joins(:organisations).where("organisations.user_id = 1")
QUERY will be 
 select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1

it will return all records of organisation related to user
and @records.map{|u|u.organisation.name}
it run QUERY like 
select * from organisations where organisations.id = x then time(hwo many organisation you have)

이 경우 총 SQL 수는 11입니다.

그러나 'includes'를 사용하면 포함 된 연결을 열망하고 메모리에 추가하고 (첫 번째로드시 모든 연결을로드) 쿼리를 다시 실행하지 않습니다

@ records = User.includes (: organisations) .where ( "organisations.user_id = 1")과 같은 include를 가진 레코드를 얻으면 쿼리는

select * from users INNER JOIN organisations ON organisations.user_id = users.id where organisations.user_id = 1
and 


 select * from organisations where organisations.id IN(IDS of organisation(1, to 10)) if 10 organisation
and when you run this 

@ records.map {| u | u.organisation.name} 쿼리가 실행되지 않습니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.