问题
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:
- article1 -
tag1andtag2, rating=3 - article2 -
tag1andBBC, rating=2 - article4 -
tag6, rating=9 - 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:
- Count the matching tags per
ArticleIDin subqueryt. LEFT JOINthe main table to it with data for secondary (blog) and tertiary (rating) sort criteria.ORDER BYthe three criteria,ctfirst,blognext,ratinglast. All of them descending (highest value first). That works for the boolean expression(a.blog = 'BBC')as well, becauseTRUE(1) sorts beforeFALSE(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