Рассмотрим простую ассоциацию ...
class Person
has_many :friends
end
class Friend
belongs_to :person
end
Какой самый чистый способ получить всех людей, у которых НЕТ друзей в ARel и / или meta_where?
А потом насчет has_many: через версию
class Person
has_many :contacts
has_many :friends, :through => :contacts, :uniq => true
end
class Friend
has_many :contacts
has_many :people, :through => :contacts, :uniq => true
end
class Contact
belongs_to :friend
belongs_to :person
end
Я действительно не хочу использовать counter_cache - и я из того, что я прочитал, не работает с has_many: through
Я не хочу извлекать все записи person.friends и проходить по ним в Ruby - мне нужен запрос / область действия, которые я могу использовать с гемом meta_search
Я не против стоимости производительности запросов
И чем дальше от реального SQL, тем лучше ...
ruby-on-rails
arel
meta-where
craic.com
источник
источник
DISTINCT
. В противном случае, я думаю, вы бы хотели нормализовать данные и индекс в этом случае. Я мог бы сделать это, создавfriend_ids
hstore или сериализованный столбец. Тогда вы могли бы сказатьPerson.where(friend_ids: nil)
not exists (select person_id from friends where person_id = person.id)
(или, возможно,people.id
илиpersons.id
, в зависимости от того, какой у вас стол.) Не уверен, что самый быстрый в конкретной ситуации, но в прошлом это работало хорошо для меня, когда я не пытался использовать ActiveRecord.Лучше:
Для hmt это в основном то же самое, вы полагаетесь на то, что у человека без друзей также не будет контактов:
Обновить
Есть вопрос о
has_one
в комментариях, так что просто обновление. Хитрость в том, чтоincludes()
ожидает имя ассоциации, ноwhere
ожидает имя таблицы. Дляhas_one
ассоциации обычно будет выражаться в единственном числе, так что меняется, ноwhere()
часть остается такой, как есть. Так что, еслиPerson
толькоhas_one :contact
тогда ваше заявление будет:Обновление 2
Кто-то спрашивал об обратном, друзья без людей. Как я прокомментировал ниже, это фактически заставило меня осознать, что последнее поле (выше
:person_id
:) не обязательно должно быть связано с возвращаемой моделью, оно просто должно быть полем в таблице соединений. Они все будут,nil
так что это может быть любой из них. Это приводит к более простому решению вышеперечисленного:И затем переключение на возвращение друзей без людей становится еще проще, вы меняете только класс впереди:
Обновление 3 - Rails 5
Спасибо @Anson за отличное решение для Rails 5 (дайте ему +1 к ответу ниже), вы можете использовать
left_outer_joins
чтобы избежать загрузки ассоциации:Я включил это здесь, чтобы люди нашли это, но он заслуживает +1 для этого. Отличное дополнение!
Обновление 4 - Rails 6.1
Спасибо Tim Park за указание, что в следующей версии 6.1 вы можете сделать это:
Благодаря посту, на который он тоже ссылался.
источник
has_one
ассоциации, вам нужно изменить имя ассоциации вincludes
вызове. Если предположить, что он былhas_one :contact
внутри,Person
тогда ваш код будетPerson.includes(:contact).where( :contacts => { :person_id => nil } )
self.table_name = "custom_friends_table_name"
), используйтеPerson.includes(:friends).where(:custom_friends_table_name => {:id => nil})
.missing
метод, чтобы сделать именно это !у smathy хороший ответ на Rails 3.
Для Rails 5 вы можете использовать,
left_outer_joins
чтобы избежать загрузки ассоциации.Проверьте API документы . Он был введен в запросе № 12071 .
источник
.includes
дополнительные затраты времени загрузки не будут чем-то, что я бы сильно беспокоился по поводу оптимизации. Ваш вариант использования может отличаться.Person.joins('LEFT JOIN contacts ON contacts.person_id = persons.id').where('contacts.id IS NULL')
он также отлично работает как область. Я делаю это все время в моих проектах Rails.includes
, все эти объекты AR загружаются в память, что может быть плохо, так как таблицы становятся все больше и больше. Если вам не нужен доступ к записи контакта,left_outer_joins
он не загружает контакт в память. Скорость запросов SQL такая же, но общее преимущество приложения намного больше.Person.where(contacts: nil)
илиPerson.with(contact: contact)
если использовать, где посягает слишком далеко на «правильность» - но учитывая тот контакт: уже анализируется и идентифицируется как ассоциация, кажется логичным, что арл мог бы легко решить то, что требуется ...Лица, у которых нет друзей
Или что есть хотя бы один друг
Вы можете сделать это с помощью Arel, настроив
Friend
И тогда, Лица, у которых есть хотя бы один друг:
Без друзей
источник
DEPRECATION WARNING: It looks like you are eager loading table(s)
Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string
Оба ответа от dmarkow и Unixmonkey дают мне то, что мне нужно - спасибо!
Я попробовал оба в моем реальном приложении и получил время для них - вот две области:
Запустил это с реальным приложением - небольшая таблица с ~ 700 записями «Персона» - в среднем за 5 прогонов
Подход Unixmonkey (
:without_friends_v1
) 813ms / запросПодход dmarkow (
:without_friends_v2
) 891мс / запрос (на 10% медленнее)Но потом мне пришло в голову, что мне не нужен звонок,
DISTINCT()...
я ищуPerson
записи с NOContacts
- поэтому они просто должны бытьNOT IN
в списке контактовperson_ids
. Итак, я попробовал эту область:Это дает тот же результат, но в среднем 425 мс / вызов - почти вдвое меньше ...
Теперь вам могут понадобиться
DISTINCT
другие похожие запросы - но в моем случае это работает нормально.Спасибо за вашу помощь
источник
К сожалению, вы, вероятно, ищете решение, включающее SQL, но вы можете установить его в области, а затем просто использовать эту область:
Затем, чтобы получить их, вы можете просто сделать это
Person.without_friends
, и вы можете связать это с другими методами Arel:Person.without_friends.order("name").limit(10)
источник
Коррелированный подзапрос NOT EXISTS должен быть быстрым, особенно по мере того, как увеличивается число строк и соотношение дочерних и родительских записей.
источник
Кроме того, чтобы отфильтровать по одному другу, например:
источник