Rails: включить против: соединения - PullRequest
336 голосов
/ 30 июля 2009

Это скорее вопрос «почему все работает так», а не вопрос «Я не знаю, как это сделать» ...

Таким образом, Евангелие при извлечении связанных записей, которое, как вы знаете, будете использовать, заключается в использовании :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 осведомлен о проблеме производительности и не включается, кроме как в определенных случаях?

Ответы [ 8 ]

173 голосов
/ 30 июля 2009

Похоже, что функциональность :include была изменена в Rails 2.1. Rails использовался для объединения во всех случаях, но из соображений производительности в некоторых случаях было изменено использование нескольких запросов. Это сообщение в блоге от Фабио Акиты содержит полезную информацию об изменениях (см. Раздел «Оптимизированная загрузка с нетерпением»).

87 голосов
/ 12 апреля 2012

.joins просто присоединяет таблицы и возвращает выбранные поля. если вы вызовете ассоциации для результата запроса объединений, он снова запустит запросы к базе данных

:includes будет загружать включенные ассоциации и добавлять их в память. :includes загружает все включенные атрибуты таблиц. Если вы вызовете ассоциации для результата запроса включения, он не будет запускать запросы

70 голосов
/ 29 сентября 2009

Разница между объединениями и включением заключается в том, что использование оператора include генерирует значительно больший запрос SQL, загружая в память все атрибуты из других таблиц.

Например, если у вас есть таблица, заполненная комментариями, и вы используете: joins => users для извлечения всей пользовательской информации в целях сортировки и т. Д., Это будет работать нормально и займет меньше времени, чем: include, но скажем хотите отобразить комментарий вместе с именем пользователя, адресом электронной почты и т. д. Чтобы получить информацию с помощью: объединений, он должен будет выполнять отдельные запросы SQL для каждого пользователя, которого выбирает, тогда как, если вы использовали: include, эта информация готова для использования.

Отличный пример:

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

52 голосов
/ 30 ноября 2010

В дополнение к соображениям производительности, есть и функциональная разница. Когда вы присоединяетесь к комментариям, вы запрашиваете посты с комментариями - внутреннее объединение по умолчанию. Когда вы включаете комментарии, вы запрашиваете все сообщения - внешнее объединение.

50 голосов
/ 22 ноября 2014

Недавно я читал больше о разнице между :joins и :includes в рельсах. Вот объяснение того, что я понял (с примерами:))

Рассмотрим этот сценарий:

  • Пользователь имеет_ множество комментариев и комментарий принадлежит_ пользователю.

  • Модель пользователя имеет следующие атрибуты: Имя (строка), Возраст (целое число). Модель Comment имеет следующие атрибуты: Content, user_id. Для комментария user_id может быть нулевым.

Присоединяется:

: объединения выполняют внутреннее соединение между двумя таблицами. Таким образом

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 (таблица пользователей). Таким образом, если вы сделаете

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 снова запустит запрос к базе данных в фоновом режиме, чтобы получить результаты

Включает в себя:

: включает выполнение левого внешнего соединения между двумя таблицами. Таким образом

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">]>

будет извлекать записи, где comments.user_id равен nil, как показано.

Более того, включает в себя загружает обе таблицы в памяти. Таким образом, если вы делаете

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

Как вы можете заметить, comment_1.user.age просто загружает результат из памяти, не отправляя запрос к базе данных в фоновом режиме.

9 голосов
/ 05 января 2016

ТЛ; др

Я сравниваю их двумя способами:

присоединяется - для условного выбора записей.

включает - при использовании ассоциации для каждого члена набора результатов.

Более длинная версия

Объединения предназначены для фильтрации набора результатов, поступающего из базы данных. Вы используете его для выполнения операций над таблицами. Думайте об этом как о предложении 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, когда вы собираетесь использовать отношение для каждого члена коллекции.

4 голосов
/ 19 марта 2014

.joins работает как объединение базы данных и объединяет две или более таблицы и извлекает выбранные данные из серверной части (базы данных).

. Включает работу в качестве левого соединения базы данных. Загружены все записи левой стороны, не имеет значения правосторонняя модель. Он используется для быстрой загрузки, поскольку он загружает все связанные объекты в память. Если мы вызываем ассоциации в результате запроса на включение, то он не запускает запрос к базе данных, он просто возвращает данные из памяти, потому что он уже загрузил данные в память.

0 голосов
/ 09 мая 2015

'объединения' просто используются для объединения таблиц, и когда вы вызываете ассоциации для объединений, он снова запускает запрос (это означает, что многие запросы будут запускаться)

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

Но с «include» будет загружать включенные ассоциации и добавлять их в память (загружать все ассоциации при первой загрузке), а не запускать запрос снова

когда вы получаете записи с такими включениями, как @ records = User.include (: организации) .where ("organisations.user_id = 1") тогда запрос будет

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} ни один запрос не сработает

...