在django ORM中使用postgresql窗口函数的干净方法?

时间:2016-03-03 19:49:27

标签: sql django postgresql orm

我希望在Django需要的一些查询中使用postgresql窗口函数,如rank()dense_rank。我让它在原始SQL中工作,但我不确定如何在ORM中执行此操作。

简化它看起来像这样:

SELECT
    id,
    user_id,
    score,
    RANK() OVER(ORDER BY score DESC) AS rank
FROM
    game_score
WHERE
    ...

你会如何在ORM中做到这一点?

在某些时候我可能还需要添加分区:|

(我们在Python 3上使用Django 1.9并且已经依赖于django.contrib.postgres功能)

4 个答案:

答案 0 :(得分:15)

自Django 2.0以来,它内置于ORM中。见window-functions

# models.py
class GameScore(models.Model):
     user_id = models.IntegerField()
     score = models.IntegerField()

# window function usage
from django.db.models.expressions import Window
from django.db.models.functions import Rank

GameScore.objects.annotate(rank=Window(
    expression=Rank(),
    order_by=F('score').desc(),
    partition_by=[F('user_id')]))

# generated sql
SELECT "myapp_gamescore"."id",
   "myapp_gamescore"."user_id",
   "myapp_gamescore"."score",
   RANK() OVER (
     PARTITION BY "myapp_gamescore"."user_id"
     ORDER BY "myapp_gamescore"."score" DESC
   ) AS "rank"
FROM "myapp_gamescore"

答案 1 :(得分:11)

有几种方法可以做到这一点:

1)使用annotate和RawSQL()。优选方法。例如:

from django.db.models.expressions import RawSQL
GameScore.objects.filter().annotate(rank=RawSQL("RANK() OVER(ORDER BY score DESC)", [])   )

2)使用GameScore.objects.filter(...)。extra()函数。由于这是一个旧的API,它的目标是在将来的某个时候被弃用,所以只有当你不能使用其他查询集方法表达你的查询时才应该使用它...但它仍然有效。示例:

GameScore.objects.filter().extra(select={'rank': 'RANK() OVER(ORDER BY score DESC)' )

通过这种方式,您可以添加分区,密集等级......,没有任何问题:

RawSQL("RANK() OVER(PARTITION BY user_id ORDER BY score DESC")

您可以访问以下数据:

game_scores = GameScore.objects.filter().extra(select={'rank': 'RANK() OVER(ORDER BY score DESC)' )

for game_score in game_scores:
    print game_score.id, game_score.rank, game_score.score

1)https://docs.djangoproject.com/es/1.9/ref/models/querysets/#annotate

2)https://docs.djangoproject.com/es/1.9/ref/models/querysets/#extra

答案 2 :(得分:6)

由于提供Func表达式Django 1.8(https://docs.djangoproject.com/en/1.10/ref/models/expressions/#func-expressions),它允许这样做更清晰,更可重用:

class Rank(Func):
    function = 'RANK'
    template = '%(function)s() OVER (ORDER BY %(expressions)s DESC)'

GameScore.objects.annotate(rank=Rank('score'))

我已将此封装到独立应用django-rank-queryhttps://pypi.python.org/pypi/django-rank-query

在路上还有本地Django实现的窗口函数: code.djangoproject.com/ticket/26608

答案 3 :(得分:3)

我假设你想要使用ORM查询,你可以为你的" game_score"设置模型。表。如果是这种情况,您可以使用.raw()查询。

The Docs

用法

sql = """
    SELECT id, user_id, score, RANK() OVER(ORDER BY score DESC) AS rank
    FROM game_score
"""
game_scores = GameScore.objects.raw(sql)
for game_score in game_scores:
    print game_score.id, game_score.rank, game_score.score

"排名"属性称为"注释"。查看更多here