我在postgresql中有两个表,一个名为ip_address
,另一个名为network
。
ip_address
表有两列:
id
是INTEGER
v4address
是INET
network
表有两列:
id
是INTEGER
v4representation
是CIDR
我希望能够选择ip_address
并急切加载ip_addresses
network
(s),而无需在表之间定义基于id
的外键关系。 id
列由与其他表无关的其他关系使用。
实现此目的的SQL是:
select * from ip_address join network on ip_address.v4address << network.v4representation;
在postgresql中,<<
运算符可用于比较INET
和CIDR
。它将匹配INET
中包含CIDR
的行。
我可以在我的property
模型上定义一个IPAddress
来完成此任务:
@property
def networks(self):
query = session.query(Network)
query = query.filter("v4representation >> :v4addr").params(v4addr=self.v4address)
return query.all()
这很有效,但是当我实际尝试在应用程序中使用此property
时,我会遇到典型的“N + 1”查询问题。我想以这样的方式定义它,以便能够急于加载IP地址网络。
我尝试使用relationship
将其定义为primaryjoin
,但无法弄清楚需要什么。我试过这个:
networks = db.relationship("Network",
primaryjoin='IPAddress.v4address << Network.v4representation',
viewonly=True)
但sqlalchemy不知道如何处理<<
运算符,所以我改用了这个:
networks = db.relationship("Network",
primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
viewonly=True)
但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.
我尝试了为foreign_key
定义relationship
的几种组合:
networks = db.relationship("Network",
primaryjoin='IPAddress.v4address.op("<<")(Network.v4representation)',
foreign_keys='[Network.v4representation]',
viewonly=True)
但sqlalchemy抛出ArgumentErrors
:
ArgumentError:关系IPAddress.networks无法根据连接条件和remote_side参数确定任何明确的本地/远程列对。考虑使用remote()注释来准确标记关系远程一侧的连接条件的那些元素。
将IPAddress.v4address
或Network.v4representation
指定为remote_side
都不会更改例外。
使用primaryjoin
/ foreign
注释remote
条件的任何尝试都没有帮助。
回到我原来的意图,我希望能够执行一个查询,它将返回ip地址并急切加载他们的网络(以及可能来自网络其他关系的数据,因为这是我的完整模式的简化)。
有人有任何建议吗?
提前致谢。
答案 0 :(得分:5)
这里缺少的部分是自定义运算符在关系框架内不起作用。为了帮助解决这个问题,我为SQLAlchemy 0.9.2添加了一个新功能,即“is_comparison”标志,并在Using custom operators in join conditions处添加了一个示例。
这是一个使用较少的公共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)
在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
)