使用HAVING选择SQLAlchemy列的计数和值

时间:2017-10-27 15:08:20

标签: python postgresql sqlalchemy

我想选择具有多个重复的同一电子邮件地址的所有联系人的计数。我无法使用PostgreSQL在SQLAlchey中使用此查询。

SELECT count(*), email FROM contact group by email having count(*) > 1

我试过了:

all_records = db.session.query(Contact).options(
    load_only('email')).group_by(Contact.email).having(
    func.count('*') > 1).all()
sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) column "contact.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT contact.id AS contact_id, contact.email AS contact_em...
           ^
[SQL: 'SELECT contact.id AS contact_id, contact.email AS contact_email \nFROM contact GROUP BY contact.email \nHAVING count(%(count_1)s) > %(count_2)s'] [parameters: {'count_1': '*', 'count_2': 1}]

我试过这个:

all_records = db.session.query(func.count(Contact.id)).options(
    load_only('email')).group_by(Contact.email).having(
    func.count('*') > 1).all()
sqlalchemy.exc.ArgumentError
sqlalchemy.exc.ArgumentError: Wildcard loader can only be used with exactly one entity.  Use Load(ent) to specify specific entities.

如果我执行原始SQL,它可以正常工作:

all_records = db.session.execute(
    "SELECT count(*), email FROM contact group by email"
    " having count(*) > 1").fetchall()

我正在使用Flask-SQLAlchemy,但这是一个最小的SQLAlchemy设置来演示这个问题:

import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Contact(Base):
    __tablename__ = 'contact'
    id = sa.Column(sa.Integer, primary_key=True)
    email = sa.Column(sa.String)

engine = sa.create_engine('postgresql:///example', echo=True)
Base.metadata.create_all(engine)
session = orm.Session(engine)
session.add_all((
    Contact(email='a@example.com'),
    Contact(email='b@example.com'),
    Contact(email='a@example.com'),
    Contact(email='c@example.com'),
    Contact(email='a@example.com'),
))
session.commit()

# first failed query
all_records = session.query(Contact).options(
    orm.load_only('email')).group_by(Contact.email).having(
    sa.func.count('*') > 1).all()

# second failed query
all_records = db.session.query(sa.func.count(Contact.id)).options(
    orm.load_only('email')).group_by(Contact.email).having(
    sa.func.count('*') > 1).all()

使用示例数据,我希望得到一个结果行3, a@example.com

2 个答案:

答案 0 :(得分:3)

只需选择文本查询中的内容:

db.session.query(func.count('*'), Contact.email).\
    group_by(Contact.email).\
    having(func.count('*') > 1).\
    all()

答案 1 :(得分:3)

您未在SQLAlchemy中构建您手动编写的相同查询。

您想要选择每个包含多个匹配项的电子邮件的计数。

TextView
q = session.query(
    db.func.count(Contact.email),
    Contact.email
).group_by(
    Contact.email
).having(
    db.func.count(Contact.email) > 1
)
print(q)

第一个查询失败,因为您查询整个模型,因此SQLAlchemy选择所有列。使用SELECT count(contact.email) AS count_1, contact.email AS contact_email FROM contact GROUP BY contact.email HAVING count(contact.email) > %(count_2)s 时,您只能选择分组列。在查询整个模型时,SQLAlchemy必须始终选择主键,group_by不会影响它。

第二个查询失败,因为load_only仅在选择整个模型时有效,但您选择了聚合和列。