如何 make/use 在 Django 中自定义数据库函数

How to make/use a custom database function in Django

序言:

这是SO中经常出现的问题:

并且可以应用于以上以及以下:

我想在 SO 文档上编写一个示例,但自从它于 2017 年 8 月 8 日关闭以来,我将遵循 this widely upvoted and discussed meta answer 的建议并将我的示例编写为自我回答 post.

当然,我也很乐意看到任何不同的方法!!


问题:

Django/GeoDjango 有一些像 Lower() or MakeValid() 这样的数据库函数,可以这样使用:

Author.objects.create(name='Margaret Smith')
author = Author.objects.annotate(name_lower=Lower('name')).get()
print(author.name_lower)

有什么方法可以使用 and/or 根据现有的数据库函数创建我自己的自定义数据库函数,例如:

如何apply/use Django/GeoDjango ORM 中的那些函数?

Django 提供了 Func() 表达式来方便在查询集中调用数据库函数:

Func() expressions are the base type of all expressions that involve database functions like COALESCE and LOWER, or aggregates like SUM.

关于如何在 Django/GeoDjango ORM 中使用数据库函数有 2 个选项:

为方便起见,我们假设模型名为 MyModel 并且子字符串存储在名为 subst:[=41 的变量中=]

from django.contrib.gis.db import models as gis_models

class MyModel(models.Model):
    name = models.CharField()
    the_geom = gis_models.PolygonField()
  1. 使用Func()直接调用函数:

    我们还需要以下内容才能使我们的查询正常工作:

    • Aggregation 为我们数据库中的每个条目添加一个字段。
    • F() which allows
    • Value() which will sanitize any given value (why is this important?)

    查询:

    MyModel.objects.aggregate(
        pos=Func(F('name'), Value(subst), function='POSITION')
    )
    
  2. 创建自己的数据库函数扩展 Func:

    我们可以扩展 Func class 来创建我们自己的数据库函数:

    class Position(Func):
        function = 'POSITION'
    

    并在查询中使用它:

    MyModel.objects.aggregate(pos=Position('name', Value(subst)))
    

GeoDjango 附录:

GeoDjango 中,为了导入 GIS 相关函数(如 PostGISTransform 函数),Func() 方法必须被GeoFunc()代替,但本质上是在相同的原则下使用的:

class Transform(GeoFunc):
    function='ST_Transform'

有更复杂的 GeoFunc 用法案例,这里出现了一个有趣的用例:


泛化自定义数据库函数附录:

如果您想创建一个自定义数据库函数(选项 2)并且您希望能够在事先不知道的情况下将它用于任何数据库,您可以使用 Funcas_<database-name>方法,前提是每个数据库中都存在你要使用的函数:

class Position(Func):
    function = 'POSITION' # MySQL method

    def as_sqlite(self, compiler, connection):
        #SQLite method
        return self.as_sql(compiler, connection, function='INSTR')

    def as_postgresql(self, compiler, connection):
        # PostgreSQL method
        return self.as_sql(compiler, connection, function='STRPOS')