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

Posted

技术标签:

【中文标题】在 django ORM 中使用 postgresql 窗口函数的干净方法?【英文标题】:Clean way to use postgresql window functions in django ORM? 【发布时间】:2016-06-17 08:08:40 【问题描述】:

我想在一些需要在 Django 中执行的查询中使用像 rank()dense_rank 这样的 postgresql 窗口函数。我让它在原始 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 功能)

【问题讨论】:

未来读者注意:Django 2.0 内置了 Window 和 Rank 函数。 从 Django 2.0 开始,Window 和 Rank 函数都内置了,请看下面我的回答。 【参考方案1】:

我假设由于您想使用 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

【讨论】:

【参考方案2】:

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

1) 使用注释和 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

【讨论】:

【参考方案3】:

由于提供了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-query: https://pypi.python.org/pypi/django-rank-query

途中还有原生 Django 实现的窗口函数: code.djangoproject.com/ticket/26608

【讨论】:

非常酷,但请注意,排序也需要可变。 @Bartvds 排序在这里是可变的。还是您的意思是 ASC/DESC?该参数需要多两行。 从 Django 2.0 开始,Window 和 Rank 函数都内置了,请看下面我的回答。 @trecouvr 是的,我们喜欢新东西,所以我将接受更改为您的答案。【参考方案4】:

从 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"

【讨论】:

以上是关于在 django ORM 中使用 postgresql 窗口函数的干净方法?的主要内容,如果未能解决你的问题,请参考以下文章

惯用/快速 Django ORM 检查 mysql/postgres 上是不是存在

postgres 上的 Django unique_together:由 ORM 或 DB 强制执行?

带有 Postgres 的 ASP.NET MVC; ORM 推荐?

如何使用 django 1.7.3/postgres 迁移在数据库中设置默认列值?

Django - Django ORM 如何管理用户在数据库中上传的表

带有时区的时间戳,使用 django ORM 未知