在 SQLAlchemy 的 HAVING() 子句中使用标签

Posted

技术标签:

【中文标题】在 SQLAlchemy 的 HAVING() 子句中使用标签【英文标题】:Using Labels in HAVING() Clause in SQLAlchemy 【发布时间】:2014-08-22 19:31:20 【问题描述】:

我正在尝试实现以下查询以在 SQLAlchemy 中处理嵌套集(请参阅here)。我正在努力解决的是如何在最后的HAVING 子句中的主SELECT 查询(取决于子SELECT 查询)中使用标记的depth 计算。

SELECT node.name, (COUNT(parent.name) - (sub_tree.depth + 1)) AS depth
FROM nested_category AS node,
    nested_category AS parent,
    nested_category AS sub_parent,
    (
            SELECT node.name, (COUNT(parent.name) - 1) AS depth
            FROM nested_category AS node,
                    nested_category AS parent
            WHERE node.lft BETWEEN parent.lft AND parent.rgt
                    AND node.name = 'PORTABLE ELECTRONICS'
            GROUP BY node.name
            ORDER BY node.lft
    )AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
    AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
    AND sub_parent.name = sub_tree.name
GROUP BY node.name
HAVING depth <= 1
ORDER BY node.lft;

使用的时候感觉很亲近:

node = aliased(Category)                                                                             
parent = aliased(Category)                                                                           
sub_parent = aliased(Category)

sub_tree = DBSession.query(node.name,                                                                
    (func.count(parent.name) - 1).label('depth')).\                                                  
    filter(node.lft.between(parent.lft, parent.rgt)).\                                               
    filter(node.name == category_name).\                                                             
    group_by(node.name).\                                                                            
    order_by(node.lft).subquery()                                                                    

children = DBSession.query(node.name,                                                                
    (func.count(parent.name) - (sub_tree.c.depth + 1)).label('depth')).\                             
    filter(node.lft.between(parent.lft, parent.rgt)).\
    filter(node.lft.between(sub_parent.lft, sub_parent.rgt)).\                                       
    filter(sub_parent.name == sub_tree.c.name).\                                                     
    group_by(node.name).having(depth <= 1).\                                                       
    order_by(node.lft).all()

但是,我最终得到了错误:

NameError: global name 'depth' is not defined

哪一种是有道理的。如果我用having(func.count('depth') &lt;= 1 替换having(depth &lt;= 1),我最终会生成以下HAVING 子句,它不会返回任何结果(其中%s 占位符是('depth',1)):

HAVING count(%s) <= %s

我真正需要的是这样阅读:

HAVING depth = 1

有人有什么想法吗?

我最后的手段是实际执行原始查询,而不是通过 ORM 层,但我真的不想这样做,因为我离得太近了......

提前致谢。

编辑:

我也试过下面的代码,但是没有返回正确的结果(好像'depth'标签总是0):

node = aliased(Category)                                                                             
parent = aliased(Category)                                                                           
sub_parent = aliased(Category)

sub_tree_depth = (func.count(parent.name) - 1).label('depth')
depth = (func.count(parent.name) - (sub_tree_depth + 1)).label('depth')

sub_tree = DBSession.query(node.name,
    sub_tree_depth).\
    filter(node.lft.between(parent.lft, parent.rgt)).\
    filter(node.name == category_name).\
    group_by(node.name).\
    order_by(node.lft).subquery()

children = DBSession.query(node.name, 
    depth).\
    filter(node.lft.between(parent.lft, parent.rgt)).\
    filter(node.lft.between(sub_parent.lft, sub_parent.rgt)).\
    filter(sub_parent.name == sub_tree.c.name).\
    group_by(node.name).having(depth <= 1).\
    order_by(node.lft).all()

由此生成的 HAVING 子句看起来像(categories_2 == parent in original query):

HAVING count(categories_2.name) - ((count(categories_2.name) - 1) + 1) <= 1

编辑:

我认为包含生成的 SQL 可能会有所帮助。

SQLAlchemy

node = aliased(Category)
parent = aliased(Category)
sub_parent = aliased(Category)

sub_tree = DBSession.query(node.name,
    (func.count(parent.name) - 1).label('depth')).\
    filter(node.lft.between(parent.lft, parent.rgt)).\
    filter(node.name == category_name).\
    group_by(node.name).\
    order_by(node.lft).subquery()

depth = (func.count(parent.name) - (sub_tree.c.depth + 1)).label('depth')
children = DBSession.query(node.name, depth).\
    filter(node.lft.between(parent.lft, parent.rgt)).\
    filter(node.lft.between(sub_parent.lft, sub_parent.rgt)).\
    filter(sub_parent.name == sub_tree.c.name).\
    group_by(node.name).having(depth <= 1).\
    order_by(node.lft)

生成的 SQL

'SELECT categories_1.name AS categories_1_name, count(categories_2.name) - (anon_1.depth + %s) AS depth 
FROM categories AS categories_1, categories AS categories_2, (SELECT categories_1.name AS name, count(categories_2.name) - %s AS depth 
FROM categories AS categories_1, categories AS categories_2 
WHERE categories_1.lft BETWEEN categories_2.lft AND categories_2.rgt AND categories_1.name = %s GROUP BY categories_1.name ORDER BY categories_1.lft) AS anon_1, categories AS categories_3 
WHERE categories_1.lft BETWEEN categories_2.lft AND categories_2.rgt AND categories_1.lft BETWEEN categories_3.lft AND categories_3.rgt AND categories_3.name = anon_1.name GROUP BY categories_1.name 
HAVING count(categories_2.name) - (anon_1.depth + %s) <= %s ORDER BY categories_1.lft' (1, 1, u'Institutional', 1, 1)

【问题讨论】:

您是否尝试过在HAVING 子句中使用整个深度作为过滤器,例如HAVING (COUNT(parent.name) - 1) = 1 感谢您的回复。我尝试使用HAVING (COUNT(parent.name) - 1) = 1,但没有返回正确的结果。然后我尝试了HAVING (COUNT(parent.name) - (sub_tree.depth + 1)) &lt;= 1,这导致了'Unknown column'sub_tree.depth'的SQL错误...... 【参考方案1】:

您的 SQL 查询使用隐式连接,在 SQLAlchemy 中您需要显式定义它们。除此之外,您的第二次尝试几乎是正确的:

node = aliased(Category)
parent = aliased(Category)
sub_parent = aliased(Category)

sub_tree = DBSession.query(node.name,
    (func.count(parent.name) - 1).label('depth')).\
    join(parent, node.lft.between(parent.lft, parent.rgt)).\
    filter(node.name == category_name).\
    group_by(node.name).\
    order_by(node.lft).subquery()

depth = (func.count(parent.name) - (sub_tree.c.depth + 1)).label('depth')
children = DBSession.query(node.name, depth).\
    join(parent, node.lft.between(parent.lft, parent.rgt)).\
    join(sub_parent, node.lft.between(sub_parent.lft, sub_parent.rgt)).\
    join(sub_tree, sub_parent.name == sub_tree.c.name).\
    group_by(node.name, sub_tree.c.depth).\
    having(depth <= 1).\
    order_by(node.lft).all()

如果生成的 SQL 中的 HAVING 子句将重复完整的表达式而不是其别名,请不要感到惊讶。那是因为那里不允许使用别名,它只是 mysql 扩展,而 SQLAlchemy 努力生成在大多数情况下都有效的 SQL。

【讨论】:

我真的很感激。但是,我确实收到了一条错误消息Unknown column 'anon_1.depth' in 'having clause'。我不能发布整个生成的 SQL,但这是我认为重要的部分:HAVING count(categories_2.name) - (anon_1.depth + 1) &lt;= 1 我更新了我的答案,起初我没有注意到缺少的连接......希望它现在可以工作。 真的感谢你的坚持。不幸的是,即使有连接,我也会收到相同的错误消息Unknown column 'anon_1.depth' in 'having clause'。为HAVING 子句生成的SQL 相同:HAVING count(categories_2.name) - (anon_1.depth + 1) &lt;= 1。同样,我不能发布整个生成的 SQL,但我可以验证它现在正在使用 INNER JOINS。 好的,我在谷歌上搜索了一下,发现 MySQL 中的 Unknown column in 'having clause' 错误意味着该列必须包含在 GROUP BY 子句中(谈论模糊的错误消息......)。我将更新我的答案以包含它,但现在我很好奇您的原始 SQL 查询是否有效。不幸的是,我手头没有任何 MySQL 安装可以自己尝试一下(我是 Postgres 人)。

以上是关于在 SQLAlchemy 的 HAVING() 子句中使用标签的主要内容,如果未能解决你的问题,请参考以下文章

SQLAlchemy(四):SQLAlchemy查询高级

使用 GROUP BY/HAVING 重构子查询?

存在带有 HAVING 子句的子查询

无法在具有聚合值的 HAVING 子句中使用来自子查询表连接的单值列

数据库子查询 ---where或having后面----列子查询-多行子查询

简单子查询的混淆 SQLAlchemy 转换