【问题标题】:SQLAlchemy query parents with ALL children matching a filterSQLAlchemy 查询所有子级匹配过滤器的父级
【发布时间】:2020-11-14 13:57:44
【问题描述】:

我正在尝试创建一个 sqlalchemy 查询 (flask-sqlalchemy),如果所有子项都匹配查询而不是 ANY,则该查询仅返回父对象。浏览器

(Parent
 .query
 .join(Child)
 .filter(Child.column == True)
 ).all()

将返回所有具有column == True 的子对象的父对象,我如何编写它以使所有子对象都必须具有column == True

------------------- 更新 --------------- ---------------

以上部分是从这段实际代码中抽象出来的。在哪里

家长 == 学生和孩子 == StudentApplication

模型层次结构如下:

用户 ---> 学生 ---> StudentApplication

我正在尝试检索有学生但申请尚未提交的用户,同时忽略至少有 1 名学生已提交申请的用户。

def _active_student_query(statement=None):
    import sqlalchemy as sa
    statement = statement or _active_user_query()
    statement = statement.join(Student).filter(Student.suspended == sa.false())
    return statement

def users_without_submitted_applications(exclude_contacted=False):
    import sqlalchemy as sa
    from sqlalchemy.orm.util import AliasedClass

# AppAlias = AliasedClass(StudentApplication)

has_any_approved_app = sa.exists(
    sa.select([])
        .select_from(Student)
        .where((StudentApplication.student_id == Student.id) &
               (StudentApplication.flags.op('&')(AppFlags.SUBMITTED) > 0),
               )
)

statement = _active_student_query()
statement = (statement
             # .join(StudentApplication)
             # .filter(User.students.any())
             # has a student and no submitted applications
             .filter(~has_any_approved_app)
             # .filter(StudentApplication.flags.op('&')(AppFlags.SUBMITTED) == 0)
             )
if exclude_contacted:
    statement = (statement
                 .join(AlertPreferences)
                 .filter(AlertPreferences.marketing_flags.op('&')(MarketingFlags.NO_APP_PING) == 0)
                 )

from lib.logger import logger
logger.info(statement.sql)
return statement

这是它生成的 SQL

SELECT users.is_active, users.flags, users.created_on, users.updated_on, users.id
FROM users JOIN student ON users.id = student.user_id 
WHERE users.is_active = true AND student.suspended = false AND NOT (EXISTS (SELECT  
FROM student_application 
WHERE student_application.student_id = student.id AND (student_application.flags & %(flags_1)s) > %(param_1)s))

【问题讨论】:

  • 所以您只希望父对象有一个孩子,且该孩子符合您的(一个)条件?
  • @GordThompson 理想情况下,我也希望能够对其应用其他过滤器,但我想过滤掉具有值为 False 的任何孩子的父母
  • 啊,好吧。更像SELECT * FROM parent WHERE ... AND parent.id NOT IN (SELECT child.parent_id FROM child WHERE child.column = False) ...?
  • @GordThompson 是的,我认为这行得通,我正在玩弄 sqlalchemy all_ 函数,试图创建一个有效的子查询。到目前为止没有运气

标签: python flask sqlalchemy flask-sqlalchemy


【解决方案1】:

如果您正在寻找所有孩子都有column = TRUE 的父母,那么这相当于所有孩子都没有column = FALSE 的父母。如果您使用 JOIN,您会遇到每个孩子而不是每个父母拥有一行的问题。因此,我建议改用WHERE EXISTS()。像这样的查询可以解决问题:

SELECT *
FROM parent
WHERE NOT EXISTS(
    SELECT
    FROM child
    WHERE child.parent_id = parent.id
      AND child.column = FALSE
)

在 Flask-SQLAlchemy 中变成:

import sqlalchemy as sa
has_any_false_children = sa.exists(
  sa.select([])
  .select_from(Child)
  .where((Child.parent_id == Parent.id) &
         (Child.column == sa.false()))
)

Parent.query.filter(~has_any_false_children)

更新

既然你说的是Users,所有Students 都完成了所有StudentApplications,我认为它应该变成

student_has_any_non_approved_app = sa.exists(
    sa.select([])
      .select_from(StudentApplication)
      .where((StudentApplication.student_id == Student.id) &
             (StudentApplication.flags.op('&')(AppFlags.SUBMITTED) > 0) &
             ((StudentApplication.flags.op('&')(AppFlags.APPROVED) == 0) |
              (StudentApplication.flags.op('&')(AppFlags.PARTIALLY_APPROVED) == 0)))
)

user_has_any_non_approved_students = sa.exists(
    sa.select([])
      .select_from(Student)
      .where((Student.user_id == User.id) &
                         (Student.suspended == sa.false()) &
             student_has_any_non_approved_app)
)

statement = (
        _active_user_query()
        .filter(User.students.any())
      # has a student and no submitted applications
      .filter(~user_has_any_non_approved_students)
)

这将返回所有用户。如果您随后想要该用户的学生,我会将其放入单独的查询中 - 并在那里应用营销标志。

statement = Student.query.filter(
    Student.user_id.in_(user_ids),
    Student.suspended == sa.false()
)

if exclude_contacted:
    statement = (statement
                 .join(AlertPreferences)
                 .filter(AlertPreferences.marketing_flags.op('&')(MarketingFlags.NO_APP_PING) == 0)
                 )

【讨论】:

  • 看起来不错,我只是遇到了为子查询创建别名的问题。 sqlalchemy.exc.ProgrammingError: (psycopg2.errors.SyntaxError) FROM 中的子查询必须有别名 LINE 2: FROM (SELECT Child.name... ^ 提示:例如 FROM (SELECT ...) [AS] foo.
  • EXISTS 中的子查询不需要别名,因为它不在 FROM 中——而且您的代码没有提及任何其他子查询。你能显示你的代码吗?最好将其添加到您的问题中,以便您可以格式化它
  • 您好,感谢您的帮助。我已将实际代码添加到我的问题中。
  • 不客气!我对您的代码有几点意见。 1) 我记得 == False 有问题 - 你确定它不应该是 == sa.false() 还是只是 ~Student.suspended? 2) & 的运算符是什么 - 你是在做按位运算还是别的意思? 3) 你不应该加入StudentApplication 以防止出现多行。删除连接并将SUBMITTED 检查移至has_any_approved_app 内部。 4)我认为用.filter(~has_any_approved_app)替换.filter(Student.query.filter(~has_any_approved_app))应该可以解决它
  • 我根据您的建议更新了代码,并添加了 sqlalchemy 正在生成的 sql。如果用户有匹配的任何学生而不是所有学生,则查询仍会返回用户。关于按位 & 运算符,这是故意的。
猜你喜欢
  • 1970-01-01
  • 2022-10-29
  • 2018-07-20
  • 2019-07-31
  • 2018-05-06
  • 2013-10-03
  • 2022-06-29
  • 1970-01-01
  • 2013-07-25
相关资源
最近更新 更多