5

I'm new to SQLAlchemy, and I would like to convert this PostgreSQL query:

SELECT product.*
, COUNT(feedback.like) FILTER (WHERE feedback.like = '1') AS like 
, COUNT(feedback.like) FILTER (WHERE feedback.like = '-1') AS unlike
FROM feedback, product
WHERE product.id = feedback.product_id
GROUP BY product.id
ORDER BY product.id;

I have already tried this:

 products = db.session.query(
            Product,
            func.count(Feedback.like > 0).label('like'),
            func.count(Feedback.like < 0).label('unlike')
            ).filter(Product.guide_name_id==id)
            .filter(Product.id == Feedback.product_id) 
            .group_by(Product.id) 
            .order_by(Product.id) 
            .all()

Thank you in advance for your help

3
  • What is the problem that you have with the current result? Commented Jan 22, 2021 at 11:48
  • @rfkortekaas When I test on my API, the response returns : - null on all fields if there is no like - 0 for like and 0 for unlike if there are likes or unlikes Commented Jan 22, 2021 at 13:22
  • One problem is your first filter, it should not be there, based on your supplied SQL, and probably guarantees there will be no output. Commented Jan 22, 2021 at 17:08

1 Answer 1

3

Thanks to @IljaEverilä's comment, here is a more direct answer:

class Product(Base):
    __tablename__ = "product"
    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)

    def __repr__(self):
        return f"<Product(name='{self.name}')>"


class Feedback(Base):
    __tablename__ = "feedback"
    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, ForeignKey(Product.id))
    like = Column(Integer)
    product = relationship(Product)


Base.metadata.create_all(engine)

with Session(engine) as session:

    # set up test data
    widget = Product(name="widget")
    session.add_all(
        [
            widget,
            Feedback(product=widget, like=1),
            Feedback(product=widget, like=1),
            Feedback(product=widget, like=-1),
            Product(name="gadget"),
        ]
    )

    # run the query
    query = (
        select(
            Product,
            func.count(Feedback.like)
            .filter(Feedback.like == 1)
            .label("likes"),
            func.count(Feedback.like)
            .filter(Feedback.like == -1)
            .label("dislikes"),
        )
        .select_from(Product)
        .outerjoin(Feedback)
        .group_by(Product)
    )
    results = session.execute(query).fetchall()
    print(results)
    # [(<Product(name='gadget')>, 0, 0), (<Product(name='widget')>, 2, 1)]

(Original answer)

I'm not sure if SQLAlchemy's postgresql dialect specifically handles COUNT … FILTER, but you can accomplish the same thing using SUM and CASE:

from sqlalchemy import __version__ as sa_version, case, Column, ForeignKey, func, Integer, String
from sqlalchemy.orm import Session

print(sa_version)  # 1.4.0b2


class Product(Base):
    __tablename__ = "product"
    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)


class Feedback(Base):
    __tablename__ = "feedback"
    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, ForeignKey(Product.id))
    like = Column(Integer)
    product = relationship(Product)


Base.metadata.create_all(engine)

with Session(engine, future=True) as session:
    widget = Product(name="widget")
    session.add_all(
        [
            widget,
            Feedback(product=widget, like=1),
            Feedback(product=widget, like=1),
            Feedback(product=widget, like=-1),
            Product(name="gadget"),
        ]
    )
    results = (
        session.query(
            Product.name,
            func.sum(case((Feedback.like > 0, 1), else_=0)).label(
                "likes"
            ),
            func.sum(case((Feedback.like < 0, 1), else_=0)).label(
                "dislikes"
            ),
        )
        .select_from(Product)
        .outerjoin(Feedback)
        .group_by(Product)
        .all()
    )
    print(results)  # [('widget', 2, 1), ('gadget', 0, 0)]
Sign up to request clarification or add additional context in comments.

2 Comments

@IljaEverilä - Thanks. I will update the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.