如何将空间 JOIN 翻译成 Django 查询语言?

Posted

技术标签:

【中文标题】如何将空间 JOIN 翻译成 Django 查询语言?【英文标题】:How could I translate a spatial JOIN into Django query language? 【发布时间】:2021-08-23 09:58:59 【问题描述】:

上下文

我有两个表 app_areaapp_point 以任何方式不相关(没有外键)期望每个表都有一个几何字段,因此我们可以分别在空间上查询它们 polygonpoint 类型。裸模型看起来像:

from django.contrib.gis.db import models

class Point(models.Model):
    # ...
    geom = models.PointField(srid=4326)

class Area(models.Model):
    # ...
    geom = models.PolygonField(srid=4326)

我想创建一个过滤掉多边形中不包含的点的查询。 如果我必须用 Postgis/SQL 语句编写它来执行这个任务,我会发出这种查询:

SELECT
    P.*
FROM
    app_area AS A JOIN app_point AS P ON ST_Contains(A.geom, P.geom);

在定义空间索引时,这既简单又高效。

我关心的是在我的 Django 应用程序中编写这个查询而不用硬编码 SQL。因此,我想使用经典的 Django 查询语法将其委托给 ORM。

问题

我在互联网上找不到这种查询的明确示例,我找到了解决方案:

要么依赖使用ForeignKeyFieldprefetch_related 的预定义关系(但在我的情况下不存在这种关系); 或者使用单个手工制作的几何图形来表示多边形(但这不是我的用例,因为我想依赖另一个表作为多边形源)。

我觉得使用 Django 绝对可以实现,但也许我对这个框架太陌生,或者它没有足够的文档记录,或者我没有找到合适的关键字来搜索它。

我可以在官方文档中找到的最好的对象是 FilteredRelation 对象,它似乎可以满足我的要求:定义 JOIN 子句的 ON 部分,但我无法正确设置,主要是我没有不了解如何填写其他表格并指向正确的字段。

from django.db.models import F, Q, FilteredRelation

query = Location.objects.annotate(
    campus=FilteredRelation(<relation_name>, condition=Q(geom__contains=F("geom")))
)

主要是relation_name这个字段让我困惑。我希望它是我要加入的表(这里是Area),但它似乎是一个预期的列名。

django.core.exceptions.FieldError: Cannot resolve keyword 'Area' into field. Choices are: created, geom, id, ...

但是这个字段列表来自Point 表。

我的问题是:如何将我的空间 JOIN 翻译成 Django 查询语言?

注意:不需要依赖FilteredRelation 对象,这只是我目前找到的最佳匹配!

更新

我可以使用extra 模拟预期的输出:

results = models.Point.objects.extra(
    where=["ST_intersects(app_area.geom, app_point.geom)"],
    tables=["app_area"]
)

它返回一个QuerySet,但它仍然需要注入普通的SQL语句,然后生成的SQL在子句方面是不等价的:

SELECT "app_point"."id", "app_point"."geom"::bytea
FROM "app_point", "app_area"
WHERE (ST_intersects(app_area.geom, app_point.geom))

还有EXPLAIN 的表演。

【问题讨论】:

【参考方案1】:

我认为,最好的解决方案是聚合这些区域,然后与这些点进行交叉。

from django.db.models import Q
from django.contrib.gis.db.models.aggregates import Union

multipolygon_area = Area.objects.aggregate(area=Union("geom"))["area"]

# Get all points inside areas
Points.objects.filter(geom__intersects=multipolygon_area)

# Get all points outside areas
Points.objects.filter(~Q(geom__intersects=multipolygon_area))

这非常有效,因为它完全是在数据库级别计算的。

The idea was found here

【讨论】:

以上是关于如何将空间 JOIN 翻译成 Django 查询语言?的主要内容,如果未能解决你的问题,请参考以下文章

django - 如何使翻译工作?

将 MySql 查询转换为 Django ORM 查询

Django 第十课 4.ORM查询操作

将 mongodb 聚合查询翻译成 Java/Kotlin Spring Data

构造函数中的 Django ModelMultipleChoiceField 更新查询集在 POST 上失败

JavaScript基础 join() 将数组连接成字符串 化零为整