Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/78.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在SQLAlchemy中的外层连接后连接_Sql_Postgresql_Join_Sqlalchemy_Outer Join - Fatal编程技术网

在SQLAlchemy中的外层连接后连接

在SQLAlchemy中的外层连接后连接,sql,postgresql,join,sqlalchemy,outer-join,Sql,Postgresql,Join,Sqlalchemy,Outer Join,假设我有一个一对多的关系,其中父母和孩子按照一些组id进行分组 注意:这个示例是我的代码的精简版本,实际上是一个多对多关系。可能有一些与问题无关的错误 group\u id用于创建新版本,因此具有相同id的节点和叶可以存在于多个组中 我想做的是比较两组,找出所有父母都变了的叶子。我尝试使用外部联接进行比较,然后使用两个联接过滤父节点: def find_changed_leaves(group_id_a, group_id_b, session): NodeA = model.Node

假设我有一个一对多的关系,其中父母和孩子按照一些
组id
进行分组

注意:这个示例是我的代码的精简版本,实际上是一个多对多关系。可能有一些与问题无关的错误

group\u id
用于创建新版本,因此具有相同
id
的节点和叶可以存在于多个组中

我想做的是比较两组,找出所有父母都变了的叶子。我尝试使用外部联接进行比较,然后使用两个联接过滤父节点:

def find_changed_leaves(group_id_a, group_id_b, session):
    NodeA = model.Node
    NodeB = aliased(model.Node, name='node_b')
    LeafA = model.Leaf
    LeafB = aliased(model.Leaf, name='leaf_b')

    query = (session.query(LeafA, LeafB)
        .outerjoin(LeafB, LeafA.id == LeafB.id)

        .join(NodeA, (LeafA.group_id == NodeA.group_id) &
                     (LeafA.parent_id == NodeA.id))
        .join(NodeB, (LeafB.group_id == NodeB.group_id) &
                     (LeafB.parent_id == NodeB.id))

        # Group membership
        .filter(LeafA.group_id == group_id_a,
                LeafB.group_id == group_id_b)

        # Filter for modified parents
        .filter(NodeA.title != NodeB.title)
    )

    return query.all()
这是可行的,但它不会显示仅在其中一个组中的叶(例如,如果将叶添加到新组中的节点)。如何显示所有叶,对其中一个组中缺少的叶返回
None


编辑:我知道有。我天真地尝试将其更改为
.outerjoin(NodeA,
),但没有任何帮助。

如评论中所述,还不完全清楚需要实现什么。尽管如此,下面的代码至少应该为您提供一些指导

首先,我不会尝试将其全部合并到一个查询中(可能使用完全联接和子查询),而是将其拆分为3个单独的查询:

  • 获取LeafA、LeafB的父项已更改的
  • 获取没有相应的
    LeafB的
    LaefA
  • 获取没有相应的
    LeafA的
    LaefB
  • 下面是应在
    sqlite
    postgresql
    中运行的代码。请注意,我添加了关系并在查询中使用它们。但您可以使用与代码片段中相同的显式连接条件

    import uuid
    
    from sqlalchemy import (
        create_engine, Column, Integer, String, ForeignKey, Text, and_,
        ForeignKeyConstraint, UniqueConstraint, exists
    )
    from sqlalchemy.orm import sessionmaker, relationship, eagerload, aliased
    from sqlalchemy.ext.declarative import declarative_base, declared_attr
    from sqlalchemy.dialects.postgresql import UUID as GUID
    
    _db_uri = 'sqlite:///:memory:'; GUID = String
    # _db_uri = "postgresql://aaa:bbb@localhost/mytestdb"
    engine = create_engine(_db_uri, echo=True)
    Session = sessionmaker(bind=engine)
    Base = declarative_base(engine)
    
    newid = lambda: str(uuid.uuid4())
    
    # define object model
    class Node(Base):
        __tablename__ = 'node'
        id = Column(GUID, default=newid, primary_key=True)
        group_id = Column(GUID, nullable=False, primary_key=True)
        # parent_id = Column(GUID)
        title = Column(Text, nullable=False)
    
    
    class Leaf(Base):
        __tablename__ = 'leaf'
        id = Column(GUID, nullable=False, primary_key=True)
        group_id = Column(GUID, nullable=False, primary_key=True)
        parent_id = Column(GUID, nullable=False)
        title = Column(Text, nullable=False)
    
        # define relationships - easier test data creation and querying
        parent = relationship(
            Node,
            primaryjoin=and_(Node.id == parent_id, Node.group_id == group_id),
            backref="children",
        )
    
        __table_args__ = (
            ForeignKeyConstraint(
                ['parent_id', 'group_id'], ['node.id', 'node.group_id']
            ),
        )
    
    
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)
    
    session = Session()
    
    
    g1, g2, l1, l2, l3 = [newid() for _ in range(5)]
    
    # Create test data
    def _add_test_data():
        n11 = Node(
            title="node1", group_id=g1,
            children=[
                Leaf(id=l1, title="g1 only"),
                Leaf(id=l3, title="both groups"),
            ]
        )
    
        n21 = Node(
            title="node1 changed", group_id=g2,
            children=[
                Leaf(id=l2, title="g2 only"),
                Leaf(id=l3, title="both groups"),
            ]
        )
    
        session.add_all([n11, n21])
        session.commit()
    
    
    def find_changed_leaves(group_id_a, group_id_b):
        """
        Leaves which are in both versions, but a `title` for their parents is changed.
        """
        NodeA = aliased(Node, name='node_a')
        NodeB = aliased(Node, name='node_b')
        LeafA = aliased(Leaf, name='leaf_a')
        LeafB = aliased(Leaf, name='leaf_b')
    
        query = (
            session.query(LeafA, LeafB)
            .filter(LeafA.group_id == group_id_a)
            # @note: group membership for LeafB is part of join now
            .join(LeafB, (LeafA.id == LeafB.id) & (LeafB.group_id == group_id_b))
    
            .join(NodeA, LeafA.parent)
            .join(NodeB, LeafB.parent)
    
            # Filter for modified parents
            .filter(NodeA.title != NodeB.title)
        )
        return query.all()
    
    
    def find_orphaned_leaves(group_id_a, group_id_b):
        """
        Leaves found in group A, but not in group B.
        """
        LeafA = aliased(Leaf, name='leaf_a')
        LeafB = aliased(Leaf, name='leaf_b')
    
        query = (
            session.query(LeafA)
            .filter(~(
                session.query(LeafB)
                .filter(LeafA.id == LeafB.id)
                .filter(group_id_b == LeafB.group_id)
                .exists()
            ))
    
            # Group membership
            .filter(LeafA.group_id == group_id_a)
        )
        return query.all()
    
    
    def find_deleted_leaves(group_id_a, group_id_b):
        a_s = find_orphaned_leaves(group_id_a, group_id_b)
        return tuple((a, None) for a in a_s)
    
    def find_added_leaves(group_id_a, group_id_b):
        b_s = find_orphaned_leaves(group_id_b, group_id_a)
        return tuple((None, b) for b in b_s)
    
    
    # add test data
    _add_test_data()
    
    # check the results
    changed = find_changed_leaves(g1, g2)
    assert 1 == len(changed)
    le, ri = changed[0]
    assert le.id == ri.id == l3
    
    added = find_added_leaves(g1, g2)
    assert 1 == len(added)
    le, ri = added[0]
    assert le is None
    assert ri.id == l2
    
    deleted = find_deleted_leaves(g1, g2)
    assert 1 == len(deleted)
    le, ri = deleted[0]
    assert le.id == l1
    assert ri is None
    

    一个扩展的(类似单元测试的)输入和期望输出的示例案例对于理解您想要实现的比较范围会有很大的帮助。谢谢!我得出了相同的结论;很高兴验证了它。感谢您做出了测试案例的努力。我应该让赏金更大:)我不知道你可以加入关系,比如
    .join(NodeA,LeafA.parent)
    。那真的很酷。
    import uuid
    
    from sqlalchemy import (
        create_engine, Column, Integer, String, ForeignKey, Text, and_,
        ForeignKeyConstraint, UniqueConstraint, exists
    )
    from sqlalchemy.orm import sessionmaker, relationship, eagerload, aliased
    from sqlalchemy.ext.declarative import declarative_base, declared_attr
    from sqlalchemy.dialects.postgresql import UUID as GUID
    
    _db_uri = 'sqlite:///:memory:'; GUID = String
    # _db_uri = "postgresql://aaa:bbb@localhost/mytestdb"
    engine = create_engine(_db_uri, echo=True)
    Session = sessionmaker(bind=engine)
    Base = declarative_base(engine)
    
    newid = lambda: str(uuid.uuid4())
    
    # define object model
    class Node(Base):
        __tablename__ = 'node'
        id = Column(GUID, default=newid, primary_key=True)
        group_id = Column(GUID, nullable=False, primary_key=True)
        # parent_id = Column(GUID)
        title = Column(Text, nullable=False)
    
    
    class Leaf(Base):
        __tablename__ = 'leaf'
        id = Column(GUID, nullable=False, primary_key=True)
        group_id = Column(GUID, nullable=False, primary_key=True)
        parent_id = Column(GUID, nullable=False)
        title = Column(Text, nullable=False)
    
        # define relationships - easier test data creation and querying
        parent = relationship(
            Node,
            primaryjoin=and_(Node.id == parent_id, Node.group_id == group_id),
            backref="children",
        )
    
        __table_args__ = (
            ForeignKeyConstraint(
                ['parent_id', 'group_id'], ['node.id', 'node.group_id']
            ),
        )
    
    
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)
    
    session = Session()
    
    
    g1, g2, l1, l2, l3 = [newid() for _ in range(5)]
    
    # Create test data
    def _add_test_data():
        n11 = Node(
            title="node1", group_id=g1,
            children=[
                Leaf(id=l1, title="g1 only"),
                Leaf(id=l3, title="both groups"),
            ]
        )
    
        n21 = Node(
            title="node1 changed", group_id=g2,
            children=[
                Leaf(id=l2, title="g2 only"),
                Leaf(id=l3, title="both groups"),
            ]
        )
    
        session.add_all([n11, n21])
        session.commit()
    
    
    def find_changed_leaves(group_id_a, group_id_b):
        """
        Leaves which are in both versions, but a `title` for their parents is changed.
        """
        NodeA = aliased(Node, name='node_a')
        NodeB = aliased(Node, name='node_b')
        LeafA = aliased(Leaf, name='leaf_a')
        LeafB = aliased(Leaf, name='leaf_b')
    
        query = (
            session.query(LeafA, LeafB)
            .filter(LeafA.group_id == group_id_a)
            # @note: group membership for LeafB is part of join now
            .join(LeafB, (LeafA.id == LeafB.id) & (LeafB.group_id == group_id_b))
    
            .join(NodeA, LeafA.parent)
            .join(NodeB, LeafB.parent)
    
            # Filter for modified parents
            .filter(NodeA.title != NodeB.title)
        )
        return query.all()
    
    
    def find_orphaned_leaves(group_id_a, group_id_b):
        """
        Leaves found in group A, but not in group B.
        """
        LeafA = aliased(Leaf, name='leaf_a')
        LeafB = aliased(Leaf, name='leaf_b')
    
        query = (
            session.query(LeafA)
            .filter(~(
                session.query(LeafB)
                .filter(LeafA.id == LeafB.id)
                .filter(group_id_b == LeafB.group_id)
                .exists()
            ))
    
            # Group membership
            .filter(LeafA.group_id == group_id_a)
        )
        return query.all()
    
    
    def find_deleted_leaves(group_id_a, group_id_b):
        a_s = find_orphaned_leaves(group_id_a, group_id_b)
        return tuple((a, None) for a in a_s)
    
    def find_added_leaves(group_id_a, group_id_b):
        b_s = find_orphaned_leaves(group_id_b, group_id_a)
        return tuple((None, b) for b in b_s)
    
    
    # add test data
    _add_test_data()
    
    # check the results
    changed = find_changed_leaves(g1, g2)
    assert 1 == len(changed)
    le, ri = changed[0]
    assert le.id == ri.id == l3
    
    added = find_added_leaves(g1, g2)
    assert 1 == len(added)
    le, ri = added[0]
    assert le is None
    assert ri.id == l2
    
    deleted = find_deleted_leaves(g1, g2)
    assert 1 == len(deleted)
    le, ri = deleted[0]
    assert le.id == l1
    assert ri is None