ActiveRecord получает рейтинг победителей веб-сайтов за награду за определенный период времени - PullRequest
2 голосов
/ 11 июня 2019

Существует модель Website, которая имеет _many Rating s с атрибутом score.

Чтобы определить 10 лучших сайтов-победителей , я могу просто рассчитать и кэшировать total_score на Сайте и сделайте заказ после этого.

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

Как мне это сделать?Сгруппировать, а затем упорядочить по количеству (что не работает - см. Ниже)?Я предполагаю, что у него около 1.000 записей веб-сайта с общим рейтингом 100.000 ...

Website.left_joins(:ratings).group('ratings.website_id').
        where(ratings: { created_at: Time.parse('2019-01-01')..Time.parse('2019-06-01') }).
        order('count(ratings.score) desc').
        limit(10)

Или создать что-то вроде снимка этого периода?

Любые предложения будут удивительными!

1 Ответ

2 голосов
/ 11 июня 2019

Если без соединений / подзапросов:

т.е. если вы запрашиваете только топ-10 (только несколько) веб-сайтов, а не тысячи website записей, или что вы не собираетесь повторять каждый из тысяч массивов.

ratings = Rating.where(
  created_at: Time.parse('2019-01-01')..Time.parse('2019-06-01')
).group(
  :website_id
).order(
  'sum(score) desc'
).select(
  :website_id
)

top_10_winner_websites = ratings.take(10).map(&:website)
# above is ordered by sum(score) desc
# so,
# top_10_winner_websites[0] has the highest sum score, while
# top_10_winner_websites[-1] has the lowest sum score amongst the 10

Внимание: обратите внимание, что приведенный выше запрос только "выбирает" ratings.website_id, а не ratings.*, что будет означать, что другие атрибуты (например, id и score) из ratings объекты будут все nil, только за исключением website_id

Если с объединениями / подзапросами:

Редактировать: TODO ниже еще не работает; может понадобиться помощь Не удалось найти / решить способ сохранения порядка website_id вне подзапроса. В данный момент занят.

Для предотвращения N + 1 запросов, если вы собираетесь повторять каждую website запись, или если вы собираетесь извлекать тысячи website записей.

top_10_winner_websites = Website.where(
  id: Rating.where(
    created_at: Time.parse('2019-01-01')..Time.parse('2019-06-01')
  ).group(
    :website_id
  ).order(
    'sum(ratings.score) desc'
  ).select(
    'website_id r'
  )
).limit(10).order('r.website_id asc')

Текущий обходной путь (для незавершенного подзапроса выше):

в качестве обходного пути для решения «сохранения порядка» вне подзапроса, а также для предотвращения N + 1 запросов:

ratings = Rating.where(
  created_at: Time.parse('2019-01-01')..Time.parse('2019-06-01')
).group(
  :website_id
).order(
  'sum(ratings.score) desc'
).select(
  :website_id
)

top_10_winner_website_ids = ratings.take(10).map(&:website_id)

top_10_winner_websites = Website.where(
  id: top_10_winner_website_ids
).sort_by do |website|
  top_10_winner_website_ids.index(website.id)
end

Редактировать: По расширенному запросу вы можете проверить и получить рейтинг сайта:

website_to_check = Website.find(1)

index = top_10_winner_websites.index{|winner_website| winner_website.id == website_to_check.id }

if index.nil?
  puts 'Website was not part of the top 10 winners in this time period'
else
  puts "Website rank for this time period was: #{index + 1}"
end

^ Если вам для этого нужна чистая проверка ранга SQL, я не слишком уверен, как это реализовать.

Редактировать: на расширенный запрос, чтобы иметь дополнительные "условия" для Website записей:

... вы все еще можете использовать joins(:website), но это не мешает N + 1 запросам по сравнению с eager_load(:website) или includes(:website), но нетерпеливая загрузка не работает из-за PG::GroupingError, но вы все еще можете предотвратить N + 1 запросов, используя обходной путь выше. Смотрите полный пример ниже:

ratings = Rating.joins(:website).where(
  created_at: Time.parse('2019-01-01')..Time.parse('2019-06-01'),
  websites: {
    some_website_attribute_1: true, # UPDATE THIS
    some_website_attribute_2: 'foobar' # UPDATE THIS
  }
).group(
  :website_id
).order(
  'sum(ratings.score) desc'
).select(
  :website_id
)

top_10_winner_website_ids = ratings.take(10).map(&:website_id)

top_10_winner_websites = Website.where(
  id: top_10_winner_website_ids
).sort_by do |website|
  top_10_winner_website_ids.index(website.id)
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...