【问题标题】:Converting LEFT OUTER JOIN query to Django orm queryset/query将 LEFT OUTER JOIN 查询转换为 Django orm 查询集/查询
【发布时间】:2015-06-25 00:48:18
【问题描述】:

给定 PostgreSQL 9.2.10、Django 1.8、python 2.7.5 和以下模型:

class restProdAPI(models.Model):
    rest_id = models.PositiveIntegerField(primary_key=True)
    rest_host = models.CharField(max_length=20)
    rest_ip = models.GenericIPAddressField(default='0.0.0.0')
    rest_mode = models.CharField(max_length=20)
    rest_state = models.CharField(max_length=20)


class soapProdAPI(models.Model):
    soap_id = models.PositiveIntegerField(primary_key=True)
    soap_host = models.CharField(max_length=20)
    soap_ip = models.GenericIPAddressField(default='0.0.0.0')
    soap_asset = models.CharField(max_length=20)
    soap_state = models.CharField(max_length=20)

以下原始查询返回我正在寻找的内容:

SELECT
    app_restProdAPI.rest_id, app_soapProdAPI.soap_id, app_restProdAPI.rest_host, app_restProdAPI.rest_ip, app_soapProdAPI.soap_asset, app_restProdAPI.rest_mode, app_restProdAPI.rest_state
FROM
    app_soapProdAPI
LEFT OUTER JOIN
    app_restProdAPI
ON
    ((app_restProdAPI.rest_host = app_soapProdAPI.soap_host)
OR
    (app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip))
WHERE 
    app_restProdAPI.rest_mode = 'Excluded';

返回如下:

 rest_id | soap_id |   rest_host   |     rest_ip    | soap_asset | rest_mode | rest_state
---------+---------+---------------+----------------+------------+-----------+-----------
   1234  |  12345  | 1G24019123ABC | 123.123.123.12 |  A1234567  | Excluded  |     Up

使用 Django 的模型和 orm 结构来完成这项工作的最佳方法是什么?

我一直在寻找可能的方法来完全没有关系地连接这两个表,但似乎没有一种干净或有效的方法来做到这一点。我也尝试过在 django 中寻找左外连接的方法,但文档又稀疏或难以破译。

我知道我可能不得不使用 Q 对象来执行我在其中的 or 子句。此外,我查看了关系,看起来foreignkey() 可能有效,但我不确定这是否是最好的方法。任何和所有的帮助将不胜感激。提前谢谢你。

** 编辑 1 **

到目前为止,Todor 提供了一种使用有效的 INNER JOIN 的解决方案。如果有人能破译那乱七八糟的内联原始 html,我可能已经找到了解决方案 HERE

** 编辑 2 **

有没有办法过滤一个字段(其中某物 = '某物'),如我上面给出的查询,托多尔的回答?我尝试了以下方法,但即使我的等效 postresql 查询按预期工作,它仍然包括所有记录。似乎我不能在我所做的地方拥有所有东西,因为当我删除一个 or 语句并只执行一个 and 语句时,它会应用排除的过滤器。

soapProdAPI.objects.extra(
    select = {
        'rest_id'    : 'app_restprodapi.rest_id',
        'rest_host'  : 'app_restprodapi.rest_host',
        'rest_ip'    : 'app_restprodapi.rest_ip',
        'rest_mode'  : 'app_restprodapi.rest_mode',
        'rest_state' : 'app_restprodapi.rest_state'
    },
    tables = ['app_restprodapi'],
    where  = ['app_restprodapi.rest_mode=%s \
               AND app_restprodapi.rest_host=app_soapprodapi.soap_host \
               OR app_restprodapi.rest_ip=app_soapprodapi.soap_ip'],
    params = ['Excluded']
    )

** 编辑 3 / 现有解决方案 **

迄今为止,Todor 使用 INNER JOIN 提供了最完整的答案,但希望这个问题能够引发人们对如何实现这一点的思考。由于这似乎天生不可能,因此欢迎任何和所有建议,因为它们可能会带来更好的解决方案。话虽如此,使用 Todor 的答案,我能够完成我需要的确切查询:

restProdAPI.objects.extra(
    select = {
        'soap_id'    : 'app_soapprodapi.soap_id',
        'soap_asset' : 'app_soapprodapi.soap_asset'
    },
    tables = ['app_soapprodapi'],
    where  = ['app_restprodapi.rest_mode = %s',
              'app_soapprodapi.soap_host = app_restprodapi.rest_host OR \
               app_soapprodapi.soap_ip   = app_restprodapi.rest_ip'
    ],
    params = ['Excluded']
    )

** TLDR **

我想将此 PostGreSQL 查询转换为 Django 提供的 ORM不使用 .raw() 或任何原始查询代码。如果这有助于实现这一点,并且从性能的角度来看,这是最好的方法,我完全愿意将模型更改为拥有外键。如果在设计方面有帮助,我将使用与 django-datatables-view 一起返回的对象。

【问题讨论】:

  • INNER JOIN 适合你而不是 LEFT JOIN 吗?我可以向您展示使用.extra 的方法,但查询的结果将更像是在您的示例中使用INNER JOIN
  • @Todor 我愿意接受并非常感谢所有符合 tldr 接受标准的建议。或者如果觉得我的验收标准不合理或错误,请说明原因,我会相应修改。

标签: python sql django django-models orm


【解决方案1】:

我认为这可能对你有用!实际上,您可以使用 Q 来构建您的查询。 我尝试了 Django shell,我创建了一些数据,然后我做了这样的事情:

restProdAPI.objects.filter(Q(rest_host=s1.soap_host)|Q(rest_ip=s1.soap_ip))

其中 s1 是一个 soapProdAPI。

这是我写的所有代码,你可以试试看能不能帮到你

from django.db.models import Q
from core.models import restProdAPI, soapProdAPI

s1 =  soapProdAPI.objects.get(soap_id=1)

restProdAPI.objects.filter(Q(rest_id=s1.soap_id)|Q(rest_ip=s1.soap_ip))

【讨论】:

  • 这是一个开始,因为它实际上完成了我没有完成的地方。现在我只需要弄清楚如何让 soap_idsoap_asset 包含在结果对象中,然后让它适用于所有记录。
【解决方案2】:

用 INNER JOIN 解决它

如果您只能使用包含相应 restProdAPIsoapProdAPI's(根据您的加入声明 -> 由主机或 ip 链接)。您可以尝试以下方法:

soapProdAPI.objects.extra(
    select = {
        'rest_id'   : "app_restProdAPI.rest_id",
        'rest_host' : "app_restProdAPI.rest_host",
        'rest_ip'   : "app_restProdAPI.rest_ip",
        'rest_mode' : "app_restProdAPI.rest_mode",
        'rest_state': "app_restProdAPI.rest_state"
    },
    tables = ["app_restProdAPI"],
    where = ["app_restProdAPI.rest_host = app_soapProdAPI.soap_host \
              OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip"]
)

如何过滤更多?

由于我们使用的是.extra,我建议您仔细阅读文档。一般来说,我们不能将.filterselect 字典中的某些字段一起使用,因为它们不是soapProdAPI 的一部分并且Django 无法解析它们。我们必须坚持.extra 中的where kwarg,因为它是一个列表,我们最好添加另一个元素。

    where = ["app_restProdAPI.rest_host = app_soapProdAPI.soap_host \
              OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip",
             "app_restProdAPI.rest_mode=%s"
    ],
    params = ['Excluded']

重复子查询

如果你真的需要所有soapProdAPI's,不管它们是否有对应的restProdAPI,我只能想到一个丑陋的例子,其中subquery为你需要的每个字段重复。

soapProdAPI.objects.extra(
    select = {
        'rest_id'   : "(select rest_id from app_restProdAPI where app_restProdAPI.rest_host = app_soapProdAPI.soap_host OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip)",
        'rest_host' : "(select rest_host from app_restProdAPI where app_restProdAPI.rest_host = app_soapProdAPI.soap_host OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip)",
        'rest_ip'   : "(select rest_ip from app_restProdAPI where app_restProdAPI.rest_host = app_soapProdAPI.soap_host OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip)",
        'rest_mode' : "(select rest_mode from app_restProdAPI where app_restProdAPI.rest_host = app_soapProdAPI.soap_host OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip)",
        'rest_state': "(select rest_state from app_restProdAPI where app_restProdAPI.rest_host = app_soapProdAPI.soap_host OR app_restProdAPI.rest_ip = app_soapProdAPI.soap_ip)"
    },
)

【讨论】:

  • 您的第一个查询出现以下错误:ProgrammingError: 关系“app_restProdAPI”不存在。 ...oapprodapi"."soap_state" FROM "app_soapprodapi" , "app_rest_... 除非我读错了,否则它看起来像是在加入问题。
  • 我认为您没有正确引用表名。检查this答案。
  • 我对文章here做了一个干净的版本。老实说,我不确定这一点。即使它有效,我也可能不会使用它。
  • 检查我编辑的答案。你基本上做对了,但它对你不起作用,因为你错过了用括号来分隔逻辑语句。如果你把它作为另一个元素添加到列表中,Django 会处理的。我一开始并不是故意使用它,因为我希望 ORhostip 之间,并且 Django 默认与 AND 连接。
  • 标记为正确答案,因为这似乎是创建关系并进行选择相关的最佳方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-04
  • 2016-04-20
  • 2015-01-10
  • 1970-01-01
  • 2018-06-10
  • 2013-03-14
  • 1970-01-01
相关资源
最近更新 更多