Select all items that have matching tags

我只是一个虾纸丫 提交于 2019-12-24 17:16:04

问题


Suppose I have the following Django models:

class Article(models.Model):
    title = models.CharField(max_length=200)
    blog = models.CharField(max_length=255)
    rating = models.IntegerField(default=0)

class ArticleTag(models.Model):
    article = models.ForeignKey(Article)
    tag = models.CharField(max_length=200)

Add some data:

            ArticleID       Rating          Blog
         -----------------------------------------
article1 ->     1             3             CNN
article2 ->     2             2             BBC
article3 ->     3             5             BBC
article4 ->     4             9             NTV


ArticleID      tag
-------------------
    1         tag1
    1         tag2
    1         tag3
    2         tag1
    2         tag4
    3         tag5
    4         tag6
    4         tag7

Suppose we have a user that likes tag1, tag2, tag6 and BBC. All the articles match the requirements, because article1 has tag1 and tag2, article4 has tag1, article2 and article3 are from BBC.

If we order them by rating: article4, article3, article1, article2.

However, I need to order items by the number of matching tags they have + blog first, and then by rating as the second ordering parameter. So I expect the results in the following order:

  1. article1 - tag1 and tag2, rating=3
  2. article2 - tag1 and BBC, rating=2
  3. article4 - tag6, rating=9
  4. article3 - BBC, rating=5

Is it possible to do this in Django? If not, what about PostgreSQL?


回答1:


The SQL query could look like this:

SELECT *
FROM   Article a
LEFT   JOIN (
   SELECT ArticleID, count(*) AS ct
   FROM   ArticleTag
   WHERE  tag IN ('tag1', 'tag2', 'tag6')   -- your tags here
   GROUP  BY ArticleID
   ) t ON t.ArticleID = a.ID
ORDER BY t.ct DESC NULLS LAST
       , (a.blog = 'BBC') DESC NULLS LAST   -- your blog here
       , rating DESC NULLS LAST;

Basically:

  1. Count the matching tags per ArticleID in subquery t.
  2. LEFT JOIN the main table to it with data for secondary (blog) and tertiary (rating) sort criteria.
  3. ORDER BY the three criteria, ct first, blog next, rating last. All of them descending (highest value first). That works for the boolean expression (a.blog = 'BBC') as well, because TRUE (1) sorts before FALSE (0) in descending order.

Important: In descending order NULL values would sort first, so NULLS LAST is needed if there can be NULL values (and does not hurt if there cannot).

  • PostgreSQL sort by datetime asc, null first?

Even if all your columns are defined NOT NULL, ct can still be NULL due to the LEFT JOIN.

If Django preserves mixed case names with double-quotes, you have to do that in SQL, too. Otherwise all identifiers are cast to lower case.




回答2:


The query could be simpler I believe :) There is no real need for a join here. Here's the sqlfiddle: http://sqlfiddle.com/#!2/1e565/10

SELECT
  article.ArticleID,
  COUNT(DISTINCT tag.tag),
  COUNT(DISTINCT article.Blog LIKE 'BBC'),
  COUNT(DISTINCT tag.tag) + COUNT(DISTINCT article.Blog LIKE 'BBC'),
  article.rating
FROM article
LEFT JOIN tag
  ON tag.ArticleID = article.ArticleID
WHERE tag.tag IN ('tag1', 'tag2', 'tag6') OR article.Blog LIKE 'BBC'
GROUP BY
  article.ArticleID,
  article.rating
ORDER BY
  COUNT(DISTINCT tag.tag) + COUNT(DISTINCT article.Blog LIKE 'BBC') DESC,
  rating DESC


来源:https://stackoverflow.com/questions/26828369/select-all-items-that-have-matching-tags

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!