Рельсы соединяются с максимальным условием - PullRequest
2 голосов
/ 08 сентября 2011

Учитывая эти две модели

class Article < AR::Base
  belongs_to :tag

  default_scope order('id DESC')
end

class Tag < AR::Base
  has_many :articles

  scope :featured, order(:ordering).limit(5)
end

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

Tag.featured.each do |tag|
  p tag.name
  p tag.articles.first.title # this will fetch the article in the tag with the highest id
end

Написанный таким образом, этот код имеет проблему запроса (n + 1) , я пытаюсь оптимизировать его, потому что он будет выполняться очень часто,Я исключил вызов includes в области действия featured, так как он загрузит все Article в первых 5 тегах (много статей ...)

В простом старом SQL я могусоединить эти две таблицы, используя запрос типа

SELECT tag_id, MAX(id) FROM articles GROUP BY tag_id
# or in Rails
Article.select('MAX(id), tag_id').group(:tag_id)
+--------+---------+
| tag_id | MAX(id) |
+--------+---------+
| 14     | 26787   |
...
| 1      | 27854   |
| 5      | 27780   |
| 0      | 10953   |
+--------+---------+

как таблицу объединения. Я могу получить все данные одним запросом.

Как перенести это в Rails и ActiveRecord?

update

Полный запрос, который мне нужно выполнить в контексте AR:

SELECT a.*, t.*
FROM tags t
JOIN (SELECT MAX(id) AS latest, tag_id FROM articles GROUP BY tag_id) j ON j.tag_id = t.id
JOIN articles p ON j.latest = a.id
LIMIT 5

Я попытался AR#find_by_sql метод с последним запросом, я получаюправильный набор результатов, но тогда я не могу перемещаться по объектам

sql = '...' # This holds the previous query
Tag.find_by_sql(sql).first.class
=> "Tag"
Tag.find_by_sql(sql).first.articles.first
=> nil # why???

update 2

Пробовал также с

Tag.joins('JOIN ... all my query join part here ...').first.articles.first
=> nil

Однако я заметил, что могуиспользовать мое поле статей напрямую как поля тегов, т.е. я могу написать

Tag.find_by_sql(...).first.title # where title is a field of Article class
Tag.joins(...).first.title # where title is a field of Article class

, но, очевидно, я не могу вызывать Article методы экземпляра.

1 Ответ

0 голосов
/ 09 сентября 2011

Нашел частичное решение моей проблемы.Я мог бы получить нужную запись с двумя запросами и по-прежнему иметь данные, загруженные как объекты AR:

# This will fetch the needed article ids
ids = Article.select('MAX(id) as max_id').group(:tag_id).map(&:max_id)
# This return the top tags each one with the article needed in articles.first
Tag.includes(:articles).where('article.id IN (?)', ids).limit(5).each do |t|
  t.name # gives the tag name
  t.articles # gives [<#Article...>] i.e. an array with a single article
end

Rails сделает три запроса:

  1. один, чтобы выбрать идентификаторы
  2. один, чтобы выбрать Tags идентификаторы в возвращенных_идентификаторах точки 1
  3. , последний из которых должен получить (очень странный) LEFT OUTER JOIN для извлечения данных из двух таблиц, используя WHERE a.id IN (ids) AND t.id IN (returned_ids) в качестве условия

Я не могу перейти с условием WHERE, например

where('article.id IN (SELECT MAX(id) from articles)')

, потому что MySQL имеет ошибку, и он будет думать, что подзапрос является производным .Похоже, что будет исправлено в 6.0 .

Все еще ищите лучшие ответы, возможно, JOIN, такой как в моем вопросе, который загружает объекты AR.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...