CardinalityViolation when trying to use nested subqueries to retrieve JSON from Postgres using SQLAlchemy

Viewed 203

I'm trying to translate the following Postgres query into Sqlalchemy 1.4:

SELECT json_build_object(
               'type', 'FeatureCollection',
               'features', json_agg(
                       json_build_object(
                           'type', 'Feature',
                           'geometry', geom,
                           'properties', (
                                select json_build_object(
                                    'id', hi.id,
                                    'responses', (
                                        select json_object_agg(e.etype, er.response)
                                        from expert e
                                            inner join expertresponse er on e.id = er.expert_id
                                        where infra_id = hi.id
                                    ),
                                    'protections', (
                                    select json_object_agg(p.ptype, i.pscore)
                                    from protection p
                                        inner join infraprotection i on p.id = i.protection_id
                                    where infra_id = hi.id
                                    )
                                )
                            )
                        )
                   )
           ) as allinfra
from hardinfra hi;

So I define the subqueries in the same order:

responses = (
    db.session.query(
        (func.json_object_agg(Expert.etype, Expert_Response.response)).label(
            "responses"
        )
    )
    .join(Expert_Response.exp)
    .filter(Expert_Response.infra_id == Hardinfra.id)
    .scalar_subquery()
)

protections = (
    db.session.query(
        (func.json_object_agg(Protection.ptype, Infra_Protection.pscore)).label(
            "protections"
        )
    )
    .join(Infra_Protection.prot)
    .filter(Infra_Protection.infra_id == Hardinfra.id)
    .scalar_subquery()
)

properties = db.session.query(
    (
        func.json_build_object(
            "id", Hardinfra.id, "protections", protections, "responses", responses
        ).label("properties")
    )
).scalar_subquery()

features = db.session.query(
    (
        func.json_build_object(
            "type",
            "Feature",
            "geometry",
            Hardinfra.geom,
            "properties",
            properties,
        ).label("features")
    )
).scalar_subquery()

q = db.session.query(
    func.json_build_object(
        "type", "FeatureCollection", "features", func.json_agg(features)
    )
)

However I get a (psycopg2.errors.CardinalityViolation) more than one row returned by a subquery used as an expression error when features is evaluated. I'm not even sure that properties and features need to be scalar subqueries (responses and protections certainly do), but I'm clearly also doing something else wrong, perhaps to do with correlating the inner Hardinfra refs to the outer ref? I'm not sure…

1 Answers

It might be caused by redundant subqueries, you could try to remove the last two subqueries.

responses = (
    db.session.query(
        (func.json_object_agg(Expert.etype, Expert_Response.response)).label(
            "responses"
        )
    )
    .join(Expert_Response.exp)
    .filter(Expert_Response.infra_id == Hardinfra.id)
    .scalar_subquery()
)

protections = (
    db.session.query(
        (func.json_object_agg(Protection.ptype, Infra_Protection.pscore)).label(
            "protections"
        )
    )
    .join(Infra_Protection.prot)
    .filter(Infra_Protection.infra_id == Hardinfra.id)
    .scalar_subquery()
)

properties = func.json_build_object(
            "id", Hardinfra.id, "protections", protections, "responses", responses
        )

features = func.json_build_object(
            "type",
            "Feature",
            "geometry",
            Hardinfra.geom,
            "properties",
            properties,
        )

q = db.session.query(
    func.json_build_object(
        "type", "FeatureCollection", "features", func.json_agg(features)
    )
)
Related