Как реализовать алгоритм связанных статей, используя эту форму совместной фильтрации - PullRequest
6 голосов
/ 30 ноября 2011

Как видно из названия, у меня проблема с реализацией алгоритма связанных статей.Позвольте мне начать с перечисления таблиц из базы данных:

[articles]
id_article
id_category
name
content
publish_date
is_deleted

[categories]
id_category
id_parent
name

[tags_to_articles]
id_tag
id_article

[tags]
id_tag
name

[articles_to_authors]
id_article
id_author

[authors]
id_author
name
is_deleted

[related_articles]
id_article_left
id_article_right
related_score

Алгоритм

В любой другой таблице, кроме related_articles, есть данные.Теперь я хочу заполнить related_articles оценками между статьями (очень важно: таблица будет работать как ориентированный график, оценка статьи A со статьей B может отличаться от оценки между B и A, см. Список).Оценка рассчитывается следующим образом:

  • , если две рассматриваемые статьи имеют одинаковую категорию, к баллу добавляется число (x)
  • для каждого общего автора, которого они имеютчисло (y) добавляется к баллу
  • для каждого общего тега, к номеру которого добавляется число (z)
  • , если мы вычисляем балл по статье A сВ статье B разница между now () и датой publish_d статьи B сгенерирует число (t), которое будет вычтено из оценки

Мой первый (неэффективный) подход

Я попытался сделать запрос, подобный следующему:

SELECT a.id, b.id, a.id_category, a.publish_date,
    b.id_category, b.publish_date,
    c.id_tag,
    e.id_author
FROM `articles` a, articles b, 
        tags_to_articles c, tags_to_articles d,
        articles_to_authors e, articles_to_authors f
WHERE a.id_article <> b.id_article AND 
(
    (a.id_article=c.id_article and c.id_tag=d.id_tag and d.id_article=b.id_article)
    OR
    (a.id=e.id_article and e.id_author=f.id_author and f.id_article=b.id_article)
    OR
    (a.id_category=b.id_category)
)

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

Есть ли другой способ?Я также открыт для настройки алгоритма или таблиц, если он получает работоспособное решение.Также стоит отметить, что подсчет баллов выполняется в кроне, конечно, я не ожидаю, что это будет выполняться при каждом запросе страницы.

Ответы [ 3 ]

4 голосов
/ 02 декабря 2011

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

-- First, let's list all tables that share a category.
SELECT   a1.id_article as 'left_article',
         a2.id_article as 'right_article',
         1 as 'score'
INTO     #tempscore
FROM     #articles a1
   INNER JOIN #articles a2 ON
         a1.id_category = a2.id_category
     AND a1.id_article <> a2.id_article

-- Now, let's add up everything that shares an author
INSERT INTO #tempscore (left_article, right_article, score)
SELECT   ata1.id_article,
         ata2.id_article,
         2
FROM     #articles_to_authors ata1
   INNER JOIN #articles_to_authors ata2 ON
         ata1.id_author = ata2.id_author

-- Now, let's add up everything that shares a a tag
INSERT INTO #tempscore (left_article, right_article, score)
SELECT   ata1.id_article,
         ata2.id_article,
         4
FROM     #tags_to_articles ata1
   INNER JOIN #tags_to_articles ata2 ON
         ata1.id_tag = ata2.id_tag

-- We haven't looked at dates, yet, but let's go ahead and consolidate what we know.
SELECT   left_article as 'left_article',
         right_article as 'right_article',
         SUM (score) as 'total_score'
INTO     #cscore
FROM     #tempscore
GROUP BY left_article,
         right_article

-- Clean up some extranneous stuff
DELETE FROM #cscore WHERE left_article = right_article

-- Now we need to deal with dates
SELECT   DateDiff (Day, art1.publish_date, art2.publish_date) as 'datescore',
         art1.id_article as 'left_article',
         art2.publish_date as 'right_article'
INTO     #datescore
FROM     #cscore
   INNER JOIN #articles art1 ON
         #cscore.left_article = art1.id_article
   INNER JOIN #articles art2 ON
         #cscore.right_article = art2.id_article
WHERE    art1.publish_date > art2.publish_date

-- And finally, put it all together
INSERT INTO #related_articles (id_article_left, id_article_right, related_score)
SELECT   s1.left_article,
         s1.right_article,
         s1.total_score + IsNull (s2.datescore, 0)
FROM     #cscore s1
   LEFT  JOIN #datescore s2 ON
         s1.left_article = s2.left_article
     AND s1.right_article = s2.right_article

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

2 голосов
/ 03 декабря 2011

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

INSERT INTO related_articles 
SELECT a_left.id_article,a_right.id_article,
       IF(a_left.id_category = a_right.id_category,x,0) + 
       IF( IFNULL(atu1.id_author,0) AND IFNULL(atu2.id_author,0), 
           IF(atu1.id_author = atu2.id_author,y,0), 0 
       ) +
       IF( IFNULL(tta1.id_tag,0) AND IFNULL(tta2.id_tag,0), 
           IF(tta1.id_tag = tta2.id_tag,z,0), 0 
       ) 
       -(CURRENT_TIMESTAMP - UNIX_TIMESTAMP(a_right.publish_date)) AS score
FROM
articles a_left join articles a_right ON a_left.id_article<>a_right.id_article 
AND aleft.id_article > CHECKPOINT_ID
LEFT OUTER JOIN articles_to_authors atu1 ON atu1.id_article  = a_left.id_article
LEFT OUTER JOIN articles_to_authors atu2 ON atu2.id_article = a_right.id_article
LEFT OUTER JOIN tags_to_articles tta1 ON tta1.id_article = a_left.id_article
LEFT OUTER JOIN tags_to_articles tta2 ON tta2.id_article = a_right.id_article

Возможно, вам понадобятся 2 дополнительных ЛЕВЫХ СОЕДИНЕНИЯ для обработки удаленных авторов. Ключом здесь является параметр CHECKPOINT_ID , который можно использовать, чтобы вы могли выполнять эту процедуру постепенно. Это позволит вам обрабатывать новые статьи. Альтернативой (хотя я не вижу причины) будет добавление условия, такого как

... ON a_left.id_article<>a_right.id_article AND 
    NOT EXISTS(SELECT id_article_left FROM
    related_articles WHERE id_article_left = a_left.id_article AND 
    id_article_right = a_right.id_article) ...
0 голосов
/ 09 декабря 2011

Я использовал метод на Sql Server

Я дал столько же связанных тегов для каждой статьи

, затем я получаю похожие статьи по тегам совпадений, больше одинаковых тегов означает больше связанных

ALTER PROCEDURE [dbo].[GetRelatedArticles]
    @ArticleLang int,
    @ArticleURI varchar(100),
    @Count int = 10
AS

SET NOCOUNT ON

DECLARE @URI dbo.URICountType;

INSERT INTO @URI([URI], [Count])
SELECT TOP (@Count) ArticleTag.ArticleURI, COUNT(ArticleTag.ArticleURI) AS ArticleCount
FROM ArticleTag WITH (NOLOCK)
INNER JOIN ArticleTag AS ArticleTags WITH (NOLOCK)
ON ArticleTags.ArticleURI = @ArticleURI
AND ArticleTag.ArticleURI <> @ArticleURI
AND ArticleTag.ArticleTag = ArticleTags.ArticleTag
GROUP BY ArticleTag.ArticleURI

SELECT Article.ArticleURI, Article.ArticleLang
FROM Article WITH (NOLOCK)

INNER JOIN (
    SELECT MIN(ABS(ArticleLang-@ArticleLang)) AS ArticleLangDifference, ArticleURI
    FROM Article WITH (NOLOCK)
    WHERE ArticleURI IN (SELECT URI FROM @URI)
    GROUP BY ArticleURI
) AS ArticleGrounp
ON Article.ArticleURI = ArticleGrounp.ArticleURI
AND ABS(Article.ArticleLang-@ArticleLang) = ArticleGrounp.ArticleLangDifference

INNER JOIN @URI AS URI
ON Article.ArticleURI = URI.URI

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