我有一个自我引用的一对一关系(使用uselist=False
),我试图在查询时急切地加载next_node
。如果我删除uselist=False
并访问next_node
作为检测列表,则列表将在原始查询中正确加载。这是一个已知问题,还是我在尝试急切加载next_node
backref关系时做错了什么?
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
backref, joinedload, relationship, scoped_session, sessionmaker)
from sqlalchemy import create_engine, Column, Integer, ForeignKey, MetaData
class Node(declarative_base()):
__tablename__ = 'nodes'
id = Column(Integer, primary_key=True)
value = Column(Integer)
prev_node_id = Column(Integer, ForeignKey('nodes.id'), unique=True)
prev_node = relationship(
'Node',
uselist=False,
remote_side=[id],
backref=backref('next_node', uselist=False))
engine = create_engine('sqlite://', echo=True)
metadata = MetaData()
with engine.begin() as connection:
metadata = Node.metadata
metadata.create_all(connection)
session = scoped_session(sessionmaker(bind=engine))
a = Node()
session.add(a)
b = Node()
session.add(b)
b.prev_node = a
session.commit()
query = session.query(Node)
query = query.options(joinedload(Node.next_node))
print("executing joined query")
for n in query:
if n.next_node is not None:
print(n.id, n.next_node.id)
print("done!")
两个选择语句中的结果:
...
executing joined query
2017-04-19 01:30:54,514 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2017-04-19 01:30:54,515 INFO sqlalchemy.engine.base.Engine SELECT nodes.id AS nodes_id, nodes.value AS nodes_value, nodes.prev_node_id AS nodes_prev_node_id, nodes_1.id AS nodes_1_id, nodes_1.value AS nodes_1_value, nodes_1.prev_node_id AS nodes_1_prev_node_id
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.prev_node_id
2017-04-19 01:30:54,515 INFO sqlalchemy.engine.base.Engine ()
(1, 2)
2017-04-19 01:30:54,517 INFO sqlalchemy.engine.base.Engine SELECT nodes.id AS nodes_id, nodes.value AS nodes_value, nodes.prev_node_id AS nodes_prev_node_id
FROM nodes
WHERE ? = nodes.prev_node_id
2017-04-19 01:30:54,517 INFO sqlalchemy.engine.base.Engine (2,)
done!
但是,如果我不使用uselist=False
标志,我会得到预期的单个查询:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
backref, joinedload, relationship, scoped_session, sessionmaker)
from sqlalchemy import create_engine, Column, Integer, ForeignKey, MetaData
class Node(declarative_base()):
__tablename__ = 'nodes'
id = Column(Integer, primary_key=True)
value = Column(Integer)
prev_node_id = Column(Integer, ForeignKey('nodes.id'), unique=True)
prev_node = relationship(
'Node',
uselist=False,
remote_side=[id],
backref=backref('next_node'))
engine = create_engine('sqlite://', echo=True)
metadata = MetaData()
with engine.begin() as connection:
metadata = Node.metadata
metadata.create_all(connection)
session = scoped_session(sessionmaker(bind=engine))
a = Node()
session.add(a)
b = Node()
session.add(b)
b.prev_node = a
session.commit()
query = session.query(Node)
query = query.options(joinedload(Node.next_node))
print("executing joined query")
for n in query:
if n.next_node is not None and len(n.next_node) > 0:
print(n.id, n.next_node[0].id)
print("done!")
单个选择语句中的结果:
...
executing joined query
2017-04-19 01:29:30,570 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2017-04-19 01:29:30,571 INFO sqlalchemy.engine.base.Engine SELECT nodes.id AS nodes_id, nodes.value AS nodes_value, nodes.prev_node_id AS nodes_prev_node_id, nodes_1.id AS nodes_1_id, nodes_1.value AS nodes_1_value, nodes_1.prev_node_id AS nodes_1_prev_node_id
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.prev_node_id
2017-04-19 01:29:30,571 INFO sqlalchemy.engine.base.Engine ()
(1, 2)
done!
答案 0 :(得分:1)
关系配置缺少参数join_depth
如果将其设置为join_depth=0
,即
prev_node = orm.relationship(
'Node', uselist=False,
remote_side=[id], join_depth=0,
backref=orm.backref('next_node', uselist=False)
)
然后你的第一个代码块应该在单个查询中eagerload next_node。
编辑:添加了可执行的python脚本&输出
在sqlalchemy 1.1.8,python 3.6.0& OS X El Capitan
# eager.py
import sqlalchemy.ext.declarative as dec
import sqlalchemy as sa
import sqlalchemy.orm as orm
Base = dec.declarative_base()
class Node(Base):
__tablename__ = 'nodes'
id = sa.Column(sa.Integer, primary_key=True)
value = sa.Column(sa.Integer)
prev_node_id = sa.Column(sa.Integer, sa.ForeignKey('nodes.id'), unique=True)
prev_node = orm.relationship(
'Node', uselist=False,
remote_side=[id],
join_depth=0, backref=orm.backref('next_node', uselist=False)
)
print('setup database & schema')
engine = sa.create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = orm.scoped_session(orm.sessionmaker(bind=engine))
a = Node()
b = Node()
b.prev_node = a
print("inserting nodes")
session.add(a)
session.add(b)
session.commit()
query = session.query(Node)
query = query.options(orm.joinedload(Node.next_node))
print('string representation of query:')
print(str(query))
print('*'*80)
print('executing joined query')
for n in query:
if n.next_node is not None:
print(n.id, n.next_node.id)
print('done!')
并且,此脚本生成以下输出:
# output of ``$ python eager.py``
setup database & schema
2017-04-19 22:23:36,491 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2017-04-19 22:23:36,491 INFO sqlalchemy.engine.base.Engine ()
2017-04-19 22:23:36,492 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2017-04-19 22:23:36,492 INFO sqlalchemy.engine.base.Engine ()
2017-04-19 22:23:36,492 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("nodes")
2017-04-19 22:23:36,493 INFO sqlalchemy.engine.base.Engine ()
2017-04-19 22:23:36,493 INFO sqlalchemy.engine.base.Engine
CREATE TABLE nodes (
id INTEGER NOT NULL,
value INTEGER,
prev_node_id INTEGER,
PRIMARY KEY (id),
UNIQUE (prev_node_id),
FOREIGN KEY(prev_node_id) REFERENCES nodes (id)
)
2017-04-19 22:23:36,493 INFO sqlalchemy.engine.base.Engine ()
2017-04-19 22:23:36,494 INFO sqlalchemy.engine.base.Engine COMMIT
inserting nodes
2017-04-19 22:23:36,497 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2017-04-19 22:23:36,498 INFO sqlalchemy.engine.base.Engine INSERT INTO nodes (value, prev_node_id) VALUES (?, ?)
2017-04-19 22:23:36,498 INFO sqlalchemy.engine.base.Engine (None, None)
2017-04-19 22:23:36,498 INFO sqlalchemy.engine.base.Engine INSERT INTO nodes (value, prev_node_id) VALUES (?, ?)
2017-04-19 22:23:36,499 INFO sqlalchemy.engine.base.Engine (None, 1)
2017-04-19 22:23:36,499 INFO sqlalchemy.engine.base.Engine COMMIT
string representation of query:
SELECT nodes.id AS nodes_id, nodes.value AS nodes_value, nodes.prev_node_id AS nodes_prev_node_id, nodes_1.id AS nodes_1_id, nodes_1.value AS nodes_1_value, nodes_1.prev_node_id AS nodes_1_prev_node_id
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.prev_node_id
********************************************************************************
executing joined query
2017-04-19 22:23:36,501 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2017-04-19 22:23:36,501 INFO sqlalchemy.engine.base.Engine SELECT nodes.id AS nodes_id, nodes.value AS nodes_value, nodes.prev_node_id AS nodes_prev_node_id, nodes_1.id AS nodes_1_id, nodes_1.value AS nodes_1_value, nodes_1.prev_node_id AS nodes_1_prev_node_id
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.prev_node_id
2017-04-19 22:23:36,501 INFO sqlalchemy.engine.base.Engine ()
1 2
done!
从日志中可以看出,数据库上只运行了1个查询:
SELECT nodes.id AS nodes_id, nodes.value AS nodes_value, nodes.prev_node_id AS nodes_prev_node_id, nodes_1.id AS nodes_1_id, nodes_1.value AS nodes_1_value, nodes_1.prev_node_id AS nodes_1_prev_node_id
FROM nodes LEFT OUTER JOIN nodes AS nodes_1 ON nodes.id = nodes_1.prev_node_id