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

13 投票
1 回答
23166 浏览
提问于 2025-04-18 11:50

我正在尝试在SQLAlchemy中实现一个处理嵌套集合的查询(可以在这里查看)。我遇到的问题是,如何在主SELECT查询中使用标记的depth计算(这个计算依赖于子SELECT查询),并在最后的HAVING子句中使用它。

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(depth <= 1)替换成having(func.count('depth') <= 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):

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)

1 个回答

17

你的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。

撰写回答