【问题标题】:Python Django: Join on the same tablePython Django:加入同一张桌子
【发布时间】:2015-12-30 16:41:55
【问题描述】:

我正在尝试使用 PostgreSQL 中的ltree 扩展来构建全文地址搜索引擎。

我的模型看起来像这样(稍微简化了):

from django.db import models


class Addresses(models.Model):
    name = models.CharField(max_length=255)
    path = models.CharField(max_length=255)

因此,此表中的数据将如下所示:

id  |   name       |  path
----------------------------
 1  |  USA         | 1
 2  |  California  | 1.2
 3  |  Los Angeles | 1.2.3

我想对每个实体的聚合名称进行全文搜索。 基本上我需要将表格中的每一行转换为下一种格式来进行搜索:

    id  |           full_name            |  path
-------------------------------------------------
  1     |  USA                           |   1
  2     |  California USA                |   1.2
  3     |  Los Angeles California USA    |   1.2.3

我正在这样做,因此用户可以执行诸如“los ang cali”之类的查询或类似的查询。 我对 raw PostgreSQL 查询没有任何问题:

SELECT *, ts_rank_cd(to_tsvector('english', full_address), query) AS rank 
FROM (SELECT s.id, s.path, array_to_string(array_agg(a.name ORDER BY a.path DESC), ' ') AS full_address
        FROM "Addresses" AS s INNER JOIN "Addresses" AS a
        ON (a.path @> s.path) GROUP BY s.id, s.path, s.name
) AS subquery, to_tsquery('english', %s) as query WHERE   to_tsvector('english', full_address) @@ query
ORDER BY rank DESC;

这很好,但是在使用 RawQuerySet 时,我无法使用 .filter().group_by()、分页等内容。

在 Django 中重现它的主要限制是 JOIN

JOIN "Addresses" AS a ON (a.path @> s.path)

它用于连接每个元素的所有祖先,然后使用array_agg()array_to_string 函数聚合它们,因此这些函数的输出可以在全文搜索中进一步使用。

如果有人对如何使用 Django ORM 实现这种事情有更好的想法,请指教。

【问题讨论】:

  • 我可能会建议为此使用 django-mptt 包。它将使您的模型具有树结构,因此您可以将 USA 作为父节点,将所有州作为它的子树,等等。使用该包,您可以在一个查询中获取子/父级,而无需向下/向上移动树。我不是 100% 确定这是您想要的,但请查看 github.com/django-mptt/django-mptt
  • 如果不是通过已知字段或使用额外/原始的东西,Django 没有办法像那样加入。
  • 我有一个想法,但我不知道@> 运算符。它在我的 postgresql 安装 (9.5) 中不存在。您的意思是 >= 吗?使用此运算符,您可以创建上面提到的表结构。
  • 它实际上是 ltree 的特定运算符 - postgresql.org/docs/9.5/static/ltree.html。这是 Django 使用 ltree 的一些解决方法 - github.com/whitglint/ltreefield
  • 您对我的回答中的非托管表有任何运气吗?

标签: python django postgresql


【解决方案1】:

总结

您需要一个由 VIEW 支持的非托管模型。

非托管模型。

通过将模型的 managed 元选项设置为 false 来实现创建非托管模型。

如果为 False,则不会进行数据库表的创建或删除操作 为此模型执行。如果模型代表一个 由其他人创建的现有表或数据库视图 方法。这是 managed=False 时的唯一区别。所有其他 模型处理的各个方面与正常情况完全相同。这个 包括

强调我的。

因此,如果您创建一个非托管模型,它可以由数据库上的视图表示,并且您可以访问其上的.filter().group_by()

视图。

视图是您的查询。

CREATE OR REPLACE view full_address_tree AS

SELECT a.*, s.id, s.path, array_to_string(array_agg(a.name ORDER BY a.path DESC), ' ') AS full_address
        FROM "Addresses" AS s INNER JOIN "Addresses" AS a
        ON (a.path @> s.path) GROUP BY s.id, s.path, s.name

创建模型

class FullAddressTree(models.Model):
    # copy paste the fields from your Addresses model here
    sid = models.IntegerField()
    sid = models.CharField()

    class Meta:
        # this is the most important part
        managed = False
        db_table = 'full_address_tree' # the name of the view

因此,现在您有了一个模型,可用于进行全文搜索,而无需求助于原始查询。因此,您可以使用 Django ORM 的全部功能。

迁移。

如果您想要迁移,您会发​​现 ./manage.py makemigrations 会导致虚拟迁移。 ./manage.py sqlmigrate 将显示此迁移没有执行任何 sql 查询。

要修复它并自动创建视图,请将 RunSQL 调用添加到该迁移中的 operations 列表。

migrations.RunSQL(''' COPY PASTE SQL QUERY FROM ABOVE ''')

注意事项

您创建的非托管模型是只读的。尝试创建、替换、更新或删除将失败。如果您需要此功能,则需要一个 INSTEAD 触发器。

【讨论】:

    【解决方案2】:

    非常感谢他们对 django-mptt 的建议 +1 @shang-wang。使用它可以解决您的问题,因为 MPTT 中的所有树操作都作为常规的QuerySet 工作,因此可以链接到annotateaggregate。我唯一不确定的是如果你的问题是插入很重。如果您只是打算一次将大量数据转储到表中,那么没什么大不了的。如果您要经常修改树,那么它可能会有点问题。有关 MPTT 是什么以及它是如何工作的详细说明http://www.sitepoint.com/hierarchical-data-database-2/

    无论如何,获取节点的所有祖先的原始问题就变成了 la_node.get_ancestors()。这绕过了您提到的连接约束,这应该可以重新制定查询的其余部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多