Рельсы:включите и:присоединяется

это скорее вопрос "почему все так работает", а не вопрос" я не знаю, как это сделать"...

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

это is принимая ярлык, потому что он тянет все комментарии сразу, но это все еще не соединение (что, похоже, говорит вся документация). Единственный способ получить соединение-использовать :joins вместо :include:

Post.all(:joins => :comments)

и журналы показывают:

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

Я что-то пропустила? У меня есть приложение с полудюжиной ассоциаций, и на одном экране я отображаю данные из всех них. Похоже, было бы лучше иметь один запрос join-ed вместо 6 человек. Я знаю, что с точки зрения производительности это не всегда лучше делать соединение, а не отдельные запросы (на самом деле, если вы собираетесь по времени, похоже, что два отдельных запроса выше быстрее, чем соединение), но после всех документов, которые я читал, я удивлен видеть :include не работает, как рекламируется.

Может Быть, Рельсы is осведомленный о проблеме производительности и не присоединяется, за исключением некоторых случаев?

8 ответов


получается, что :include функциональность была изменена с помощью Rails 2.1. Rails использовался для соединения во всех случаях, но по соображениям производительности он был изменен на использование нескольких запросов в некоторых случаях. этот блог Фабио Акита имеет хорошую информацию об изменении (см. раздел "оптимизированная нетерпеливая загрузка").


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

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

мой блоге имеет некоторое подробное объяснение различий


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

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

пример:

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


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


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

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

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

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

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

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

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

он будет получать записи, где комментарии.user_id-это ноль показанный.

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

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

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


tl; dr

я противопоставляю их двумя способами:

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

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

версия

соединения предназначены для фильтрации результирующего набора, поступающего из базы данных. Вы используете его для выполнения операций set на вашем столе. Подумайте об этом как о предложении where, которое выполняет set теория.

Post.joins(:comments)

это то же самое, что

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

за исключением того, что если есть более одного комментария, вы получите дубликаты сообщений с объединениями. Но каждый пост будет пост, что комментарии. Вы можете исправить это с помощью distinct:

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 когда вы собираетесь использовать связь для каждого члена коллекции.


.joins работает как database join и объединяет две или более таблицы и извлекает выбранные данные из бэкэнда (базы данных).

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


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

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" будет нетерпеливо загружать включенные ассоциации и добавлять их в память (загружать все ассоциации при первой загрузке), а не запускать запрос снова

когда вы получаете записи включает в себя как @учетная документация= Пользователь.включает (организации).где ("организации.функция 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.карта{|u|u.организация.имя} ни один запрос не будет срабатывать