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

318

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

Таким образом, Евангелие по вытаскиванию связанных записей, которые вы знаете, вы собираетесь использовать, это использовать :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 NOW, он includes (для тех, кто читает это)
  • 0
    Также есть сейчас: preload и: eager_load blog.bigbinary.com/2013/07/01/…
Теги:
database
join
rails-activerecord

8 ответов

173
Лучший ответ

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

  • 2
    Смотрите: samsaffron.com/archive/2008/03/15/…
  • 0
    Это очень полезно, спасибо. Я хотел бы, однако, чтобы был способ заставить Rails выполнить объединение даже без «где», которое этого требует. В некоторых случаях вы знаете, что объединение будет более эффективным и не повлечет за собой риск дублирования.
Показать ещё 2 комментария
71

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

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

Мой сообщение в блоге содержит подробное объяснение различий

64

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

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

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

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

50

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

43

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

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

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

  • Модель пользователя имеет следующие атрибуты: Name (string), Age (integer). Модель Comment имеет следующие атрибуты: 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(таблица 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 снова запустит запрос базы данных в фоновом режиме, чтобы получить результаты

Включает:

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

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 равно нулю, как показано.

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

comment_1 = Comment.includes(:user).first

comment_1.user.age
#=> 24

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

  • 0
    Это для Rails 4?
  • 0
    @HunterStevens: Да, это так
4

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

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

3

ТЛ; дг

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

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

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

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

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

  • 0
    Это distinct меня каждый раз. Спасибо!
0

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

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(: организации).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.organisation.name}   запрос не будет срабатывать

Ещё вопросы

Сообщество Overcoder
Наверх
Меню