[英]SQLAlchemy, eager load postgresql INET/CIDR relationships
I have two tables in postgresql, one named ip_address
, and another named network
. 我在postgresql中有两个表,一个表名为
ip_address
,另一个表名为network
。
The ip_address
table has two columns: ip_address
表具有两列:
id
is an INTEGER
id
是一个INTEGER
v4address
is an INET
v4address
是一个INET
The network
table has two columns: network
表有两列:
id
is an INTEGER
id
是一个INTEGER
v4representation
is a CIDR
v4representation
是CIDR
I want to be able to select an ip_address
and eagerly load the ip_addresses
network
(s) without having to define an id
based foreign key relationship between the tables. 我希望能够选择一个
ip_address
并热切加载ip_addresses
network
而不必在表之间定义基于id
的外键关系。 The id
columns are used by other, unrelated relationships to other tables. id
列由与其他表的其他不相关关系使用。
The SQL to accomplish this is: 完成此任务的SQL是:
select * from ip_address join network on ip_address.v4address << network.v4representation;
In postgresql, the <<
operator can be used to compare an INET
and a CIDR
. 在postgresql中,
<<
操作符可用于比较INET
和CIDR
。 It will match rows where the INET
is contained within the CIDR
. 它将匹配
CIDR
包含INET
行。
I can define a property
on my IPAddress
model which will accomplish this: 我可以在
IPAddress
模型上定义一个property
来完成此任务:
@property
def networks(self):
query = session.query(Network)
query = query.filter("v4representation >> :v4addr").params(v4addr=self.v4address)
return query.all()
This works, but then when I'm actually attempting to use this property
in an application, I experience the typical "N + 1" queries problem. 这可行,但是当我实际尝试在应用程序中使用此
property
时,我遇到了典型的“ N + 1”查询问题。 I'd like to define this in such a way as to be able to eager load an ip addresses networks. 我想以一种能够加载IP地址网络的方式来定义它。
I've tried to define it as a relationship
using primaryjoin
, but can't figure out what's needed. 我尝试使用
primaryjoin
将其定义为relationship
,但无法弄清楚需要什么。 I've tried this: 我已经试过了:
networks = db.relationship("Network",
primaryjoin='IPAddress.v4address << Network.v4representation',
viewonly=True)
But sqlalchemy doesn't know what to do with the <<
operator, so I've switched to this: 但是sqlalchemy不知道如何处理
<<
操作符,因此我切换到了这个操作:
networks = db.relationship("Network",
primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
viewonly=True)
But sqlalchemy throws an ArgumentError
: 但是sqlalchemy抛出
ArgumentError
:
ArgumentError: Could not locate any relevant foreign key columns for primary join condition 'public.ip_address.v4address << public.network.v4representation' on relationship IPAddress.networks. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or are annotated in the join condition with the foreign() annotation.
I've tried several combinations of defining a foreign_key
for the relationship
: 我尝试了几种定义
relationship
的foreign_key
组合:
networks = db.relationship("Network",
primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
foreign_keys='[Network.v4representation]',
viewonly=True)
But sqlalchemy throws ArgumentErrors
: ArgumentError: Relationship IPAddress.networks could not determine any unambiguous local/remote column pairs based on join condition and remote_side arguments. 但是sqlalchemy抛出
ArgumentErrors
:ArgumentError:关系IPAddress.networks无法基于联接条件和remote_side参数确定任何明确的本地/远程列对。 Consider using the remote() annotation to accurately mark those elements of the join condition that are on the remote side of the relationship. 考虑使用remote()批注来准确标记关系的远程端上的联接条件元素。
Neither specifying IPAddress.v4address
or Network.v4representation
as a remote_side
changes the exception. 将
IPAddress.v4address
或Network.v4representation
指定为remote_side
都不会更改该异常。
No attempts at annotating the primaryjoin
condition with foreign
/ remote
has helped either. 尝试使用
foreign
/ remote
注释primaryjoin
条件也没有任何帮助。
Coming back to my original intent, I want to be able to perform a query which will return ip addresses and eager load their networks (and possibly data from the networks other relations, as this is a simplification of my full schema). 回到我最初的意图,我希望能够执行一个查询,该查询将返回ip地址并渴望加载其网络(以及可能来自网络其他关系的数据,因为这是我完整架构的简化)。
Does anyone have any suggestions? 有没有人有什么建议?
Thanks in advance. 提前致谢。
the missing piece here is that the custom operator isn't working within the relationship framework. 这里缺少的部分是自定义运算符不在关系框架内工作。 To assist in this case I've added a new feature for SQLAlchemy 0.9.2 which is the "is_comparison" flag, and an example is here, at Using custom operators in join conditions .
为了在这种情况下提供帮助,我为SQLAlchemy 0.9.2添加了一个新功能,即“ is_comparison”标志,此处是在联接条件下使用自定义运算符的示例。
Here is a version that accomplishes this same result using less public APIs, which will work in 0.8 also: 这是一个使用较少的公共API即可达到相同结果的版本,该API也可以在0.8中使用:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.postgresql import INET, CIDR
Base = declarative_base()
# workaround before 0.9.2
from sqlalchemy.sql import operators
is_contained_by = operators.custom_op("<<")
operators._comparison.add(is_contained_by)
class IPA(Base):
__tablename__ = 'ip_address'
id = Column(Integer, primary_key=True)
v4address = Column(INET)
network = relationship("Network",
primaryjoin=lambda: is_contained_by(
IPA.v4address,
(foreign(Network.v4representation))
),
viewonly=True
)
class Network(Base):
__tablename__ = 'network'
id = Column(Integer, primary_key=True)
v4representation = Column(CIDR)
print Session().query(IPA).join(IPA.network)
In 0.9.2 and greater, it can be done as: 在0.9.2及更高版本中,可以这样执行:
class IPA(Base):
__tablename__ = 'ip_address'
id = Column(Integer, primary_key=True)
v4address = Column(INET)
network = relationship("Network",
primaryjoin="IPA.v4address.op('<<', is_comparison=True)"
"(foreign(Network.v4representation))",
viewonly=True
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.