Как получить ТОП 10 тегов, связанных с ранжируемой моделью данных?- Расширенная проблема MySQL или ActiveRecord Query - PullRequest
2 голосов
/ 02 апреля 2011

Я столкнулся с камнем преткновения с расширенными ActiveRecord и / или SQL-запросами, которые имеют отношение к тегам, ранжированию, вы называете это.Я надеюсь, что вы, гуру MySQL или Rails 3, можете помочь мне решить эту проблему.

Я заранее извиняюсь за длинный пост:)

Сначала приведу фрагмент, описывающий мою модель данных

Модель данных

AggregateData - модель содержит набор атрибутов данных ( provider_datas ), которые поступают от разных поставщиков данных, и имеет вычисленный балл атрибут, используемый для ранжирования

class AggregateData < ActiveRecord::Base
  # has a pre-populated integer attribute 'score'
  has_many :provider_datas

  # find profiles with top 10 score for specified tag      
  # this is mainly used to determine top 10 scores for later comparison
  # Since this is grouped by score, the actual number of profiles 
  # that have these score may be larger
  def self.find_top_10_by_tag(tag)
    joins(:provider_data_tags)                          \
    .where(:provider_data_tags=>{:tag_id => tag.id})    \
    .group('aggregate_data.score')                      \
    .order('aggregate_data.score DESC')                 \
    .limit(10)
  end

  # simple ranking algorithm, 
  # tells you how many AggregateDatas have better score than this one
  def ranking
    self.connection.select_value("SELECT COUNT(*) + 1 AS ranking \
      FROM aggregate_datas              \
      WHERE aggregate_datas.score > \
      (SELECT aggregate_datas.score FROM aggregate_data         \
        WHERE aggregate_datas.id = #{self.id})").to_i
  end
end

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

class ProviderData < ActiveRecord::Base
  belongs_to :aggregate_data
  has_many :provider_data_tags
  has_many :tags, :through => :provider_data_tags
end

Tag - это простая модель, содержащая атрибут name и many_to_manyсвязь с ProviderData .Обратите внимание на функцию поиска, чтобы получить все теги, связанные с предоставленными AggregateData

class Tag < ActiveRecord::Base  
  has_many :provider_data_tags
  has_many :provider_datas, :through => :provider_data_tags

  def self.find_by_aggregate_data(ag_data)
    joins(:provider_datas).where(:provider_datas =>{:aggregate_data_id => ag_data.id})
  end
end

Проблема: 10 лучших тегов для указанного AggregateData

Итак, в моей модели данных AggregateData имеет балл , и вы можете найти, какие теги, связанные с этим AggregateData , используют область или функцию Tag.find_by_aggregate_data выше

Мне нужно получить ТОП-10 тегов за AggregateData .

Что это означает, что мне нужно получить подмножество всех тегов, связанных с AggregateData , для которого оценка этого AggregateData входит в топ-10 оценок всех AggregateDatas , связанный с этим конкретным тегом.

Так что, если у этого AggregateData есть теги "java" , "ruby" , "javascript" , "html" , "css" , а данные AggregateData имеют самый высокий балл из всех AggregateDatas с тегом "ruby" и наибольшим результатом из всех AggregateDatas с тегом "javascript" , но не самым высоким показателем для "java" или "html" или "css" , тогда эта функция / область действия / запрос будет возвращать теги "ruby" и "javascript"

В этом решении предпочтительно использовать нотацию ActiveRecord / AREL, но я открыт для предложений SQL, которые я могу адаптировать к AR самостоятельно.

1 Ответ

1 голос
/ 11 апреля 2011

Вам нужно будет использовать «хитрость ранжирования» в Mysql в подзапросе, как описано в Как выполнить групповое ранжирование в MySQL .Затем в своем содержащем запросе присоединитесь к результатам подзапроса te и просто добавьте, что ранг должен быть # 1, и он должен отображать текущий элемент AggregateData.Это предполагает, что я правильно интерпретирую, вы хотите вернуть только те теги, где теги, в которых текущий объект AggregateData находится для тега # 1.

Вот идея сценария, который вы можете использовать в MySQL.Это может быть не совсем верно, но я думаю, что это передает идею.В основном вам нужно использовать некоторые пользовательские переменные для ранжирования элементов.

SET @last_tag_id = 0;

SELECT tag_id
FROM (
    SELECT tags.id AS tag_id, aggregate_datas.id AS agg_data_id, aggregate_datas.score, 
        (@rank := if(@last_tag_id = tags.id, @rank + 1, 0)) AS rank, 
        @last_tag_id := tags.id
    FROM aggregate_datas
    INNER JOIN provider_data_tags
    ON provider_data_tags.aggregate_data_id = aggregate_datas.id
    INNER JOIN tags
    ON tags.id = provider_data_tags.tag_id
    ORDER BY tags.id, aggregate_datas.score
) tag_ranks
WHERE tag_ranks.rank <= 10
AND agg_data_id = ?;
...