python-3.x SQLAlchemy连接2 CTE导致ON子句不明确

shstlldc  于 2022-12-20  发布在  Python
关注(0)|答案(2)|浏览(113)

我尝试检索发布版本的最大版本,如果资源没有发布版本,则检索最高版本。我使用3个CTE来查找这些值,一个用于获取发布的最大版本,第二个用于获取总体最大版本,最后第三个用于执行外部连接,如果存在最高发布版本,则生成最高发布版本,如果不存在最高版本。
我在SQLAlchemy中遇到的问题是试图连接前2个CTE,以便我可以为版本的每个父版本生成一个结果。
预期的查询如下所示:

WITH highest_published AS (
    SELECT parent_id AS parent_id, MAX(subversion) AS m_version
    FROM child_version
    WHERE published AND NOT deleted
    GROUP BY parent_id
),
highest_unpublished AS (
    SELECT parent_id AS parent_id, MAX(subversion) AS m_version
    FROM child_version
    WHERE NOT deleted
    GROUP BY parent_id
),
max_versions AS (
    SELECT 
    CASE  WHEN hp.parent_id IS NOT NULL THEN hp.parent_id ELSE hu.parent_id END AS parent_id,
    CASE WHEN hp.m_version IS NOT null THEN hp.m_version ELSE hu.m_version END AS m_version
    FROM highest_unpublished AS hu
    LEFT OUTER JOIN highest_published AS hp ON hp.parent_id=hu.parent_id
)
SELECT child_version.id, child_version.parent_id FROM child_version 
JOIN max_versions ON child_version.parent_id=max_versions.parent_id 
AND child_version.subversion=max_versions.m_version
ORDER BY child_version.parent_id

这是使用ORM的SA代码,我希望生成以下代码:

from sqlalchemy.dialects.postgresql import UUID
import uuid

class ChildVersion(db.Model):
    id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    deleted = db.Column(db.Boolean, default=False, nullable=False)
    parent_id = db.Column(UUID(as_uuid=True), db.ForeignKey(Parent.id), nullable=True, index=True)
    subversion = db.Column(db.Integer, default=0, nullable=False)
    published = db.Column(db.Boolean, default=False, nullable=False)

highest_published_version = ChildVersion.query.with_entities(
    ChildVersion.parent_id.label('parent_id'),
    sa.func.max(ChildVersion.subversion).label('m_version'),
    ).filter(
        ChildVersion.published, ~ChildVersion.deleted
    ).group_by(ChildVersion.parent_id).cte(name='highest_published')

highest_unpublished_version = ChildVersion.query.with_entities(
    ChildVersion.parent_id.label('parent_id'),
    sa.func.max(ChildVersion.subversion).label('m_version'), 
    ).filter(
        ~ChildVersion.deleted
    ).group_by(ChildVersion.parent_id).cte(name='highest_unpublished')

versions = db.session.query(highest_unpublished_version).with_entities(
    sa.case(
        (highest_published_version.c.parent_id.is_not(None), highest_published_version.c.parent_id), 
        else_=highest_unpublished_version.c.parent_id).label('parent_id'),
    sa.case(
        (highest_published_version.c.m_version.is_not(None), highest_published_version.c.m_version), 
        else_=highest_unpublished_version.c.m_version).label('m_version'),
    ).join(highest_published_version, sa.and_(highest_unpublished_version.c.parent_id==highest_published_version.c.parent_id,
                highest_unpublished_version.c.m_version==highest_published_version.c.m_version),
            isouter=True
    ).cte(name='max_versions')

但是,我收到一个错误,其中我的连接是不明确的:

Don't know how to join to <sqlalchemy.sql.selectable.CTE at 0x10b2c3ee0; highest_published>. Please use the
.select_from() method to establish an explicit left side, as well as providing an explicit ON clause if not present
already to help resolve the ambiguity.

Using the sqlalchemy.select to attempt to join the CTE results in invalid SQL.
一个三个三个一个
任何帮助将不胜感激!

js81xvg6

js81xvg61#

我创建了一个测试脚本,看起来可以工作。没有实际数据很难判断应该发生什么。我直接使用SA,没有 flask 。而且我刚刚为Parent类创建了一个存根。我使用了新的select()和SQLAlchemy版本1.4/2.0。

import sys
import uuid

from sqlalchemy import (
    create_engine,
    Integer,
    ForeignKey,
    Boolean,
)
from sqlalchemy.schema import (
    Column,
)
from sqlalchemy.sql import select, and_, func, case
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import Session
from sqlalchemy.dialects.postgresql import UUID

Base = declarative_base()

username, password, db = sys.argv[1:4]

engine = create_engine(f"postgresql+psycopg2://{username}:{password}@/{db}", echo=True)



class Parent(Base):
    __tablename__ = "parent"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)

class ChildVersion(Base):

    __tablename__ = "child"
    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    deleted = Column(Boolean, default=False, nullable=False)
    parent_id = Column(UUID(as_uuid=True), ForeignKey(Parent.id), nullable=True, index=True)
    subversion = Column(Integer, default=0, nullable=False)
    published = Column(Boolean, default=False, nullable=False)

Base.metadata.create_all(engine)

with Session(engine) as session, session.begin():

    highest_published = select(
        ChildVersion.parent_id.label('parent_id'),
        func.max(ChildVersion.subversion).label('m_version')
    ).select_from(
        ChildVersion
    ).where(
        ChildVersion.published,
        ~ChildVersion.deleted
    ).group_by(
        ChildVersion.parent_id
    ).cte(name='highest_published')

    highest_unpublished = select(
        ChildVersion.parent_id.label('parent_id'),
        func.max(ChildVersion.subversion).label('m_version'),
    ).select_from(
        ChildVersion
    ).where(
        ~ChildVersion.deleted
    ).group_by(
        ChildVersion.parent_id
    ).cte('highest_unpublished')

    max_versions = select(
        case(
            (highest_published.c.parent_id.is_not(None), highest_published.c.parent_id),
            else_=highest_unpublished.c.parent_id).label('parent_id'),
        case(
            (highest_published.c.m_version.is_not(None), highest_published.c.m_version),
            else_=highest_unpublished.c.m_version).label('m_version'),
    ).select_from(
        highest_unpublished
    ).outerjoin(
        highest_published,
        highest_unpublished.c.parent_id==highest_published.c.parent_id,
        # Not in original SQL?
        highest_unpublished.c.m_version==highest_published.c.m_version
    ).cte(name='max_versions')

    versions_with_ids = select(
        ChildVersion.id,
        ChildVersion.parent_id
    ).select_from(
        ChildVersion
    ).join(
        max_versions, and_(
            ChildVersion.parent_id == max_versions.c.parent_id,
            ChildVersion.subversion == max_versions.c.m_version
        )
    ).order_by(ChildVersion.parent_id)

    session.execute(versions_with_ids)

通过SQLAlchemy的echo=True生成SQL。

WITH highest_unpublished AS 
(SELECT child.parent_id AS parent_id, max(child.subversion) AS m_version 
FROM child 
WHERE NOT child.deleted GROUP BY child.parent_id), 
highest_published AS 
(SELECT child.parent_id AS parent_id, max(child.subversion) AS m_version 
FROM child 
WHERE child.published AND NOT child.deleted GROUP BY child.parent_id), 
max_versions AS 
(SELECT CASE WHEN (highest_published.parent_id IS NOT NULL) THEN highest_published.parent_id ELSE highest_unpublished.parent_id END AS parent_id, CASE WHEN (highest_published.m_version IS NOT NULL) THEN highest_published.m_version ELSE highest_unpublished.m_version END AS m_version 
FROM highest_unpublished LEFT OUTER JOIN highest_published ON highest_unpublished.parent_id = highest_published.parent_id)
 SELECT child.id, child.parent_id 
FROM child JOIN max_versions ON child.parent_id = max_versions.parent_id AND child.subversion = max_versions.m_version ORDER BY child.parent_id```
llew8vvj

llew8vvj2#

我可以按照错误消息中的建议使用select_from来解决这个问题。

highest_published_version = ChildVersion.query.with_entities(
    ChildVersion.competency_path_id.label('parent_id'),
    sa.func.max(ChildVersion.subversion).label('m_version'),
    ).filter(
        ChildVersion.published, ~ChildVersion.deleted
    ).group_by(ChildVersion.competency_path_id).cte(name='highest_published')

highest_unpublished_version = ChildVersion.query.with_entities(
    ChildVersion.competency_path_id.label('parent_id'),
    sa.func.max(ChildVersion.subversion).label('m_version'), 
    ).filter(
        ~ChildVersion.deleted
    ).group_by(ChildVersion.competency_path_id).cte('highest_unpublished')

versions = db.session.query(highest_published_version, highest_unpublished_version).select_from(highest_unpublished_version).with_entities(
    sa.case(
        (highest_published_version.c.parent_id.is_not(None), highest_published_version.c.parent_id), 
        else_=highest_unpublished_version.c.parent_id).label('parent_id'),
    sa.case(
        (highest_published_version.c.m_version.is_not(None), highest_published_version.c.m_version), 
        else_=highest_unpublished_version.c.m_version).label('m_version'),
).join(highest_published_version, sa.and_(highest_unpublished_version.c.parent_id==highest_published_version.c.parent_id,
            highest_unpublished_version.c.m_version==highest_published_version.c.m_version),
        isouter=True
).cte(name='max_versions')

相关问题