递归SQL查询的语法错误和SQLAlchemy的排序依据

时间:2018-07-05 10:17:02

标签: python sql sqlite sqlalchemy common-table-expression

我想在页面https://www.sqlite.org/lang_with.html上显示的示例查询中使用SQLAlchemy进行查询:

WITH RECURSIVE under_alice(name,level) AS (
    VALUES('Alice',0)

    UNION ALL

    SELECT org.name, under_alice.level+1
      FROM org JOIN under_alice ON org.boss=under_alice.name
     ORDER BY 2 DESC
)

SELECT substr('..........',1,level*3) || name FROM under_alice;

我正在尝试执行一个自我递归查询,该查询属于SQLite的语法错误:

  

OperationalError:“(”:语法错误附近的(sqlite3.OperationalError)

如果SQLAlchemy在递归查询中为底部查询的时间编译SQL语句加括号时引发异常,如果它使用排序环。如何从SQL中删除括号?

再现异常:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)


class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer, nullable=True)
    name = db.Column(db.String(100), nullable=False)

    def __repr__(self):
        return f'<Category {self.name}>'



import logging

logging.basicConfig()
logger = logging.getLogger('sqlalchemy.engine')
logger.setLevel(logging.INFO)

db.create_all()

类别数据:

data = [
    {'parent_id': None, 'name': 'Everything for rehabilitation'},
    {'parent_id': 1,    'name': 'Wheelchairs'},
    {'parent_id': 1,    'name': 'Wheelchairs 2'},
    {'parent_id': 1,    'name': 'Scooters for the disabled'},

    {'parent_id': 2,    'name': 'Active wheelchairs'},
    {'parent_id': 2,    'name': 'Children wheelchairs'},
    {'parent_id': 2,    'name': 'Children wheelchairs 2'},
    {'parent_id': 2,    'name': 'Wheelchair Otto Bock'},
    {'parent_id': 2,    'name': 'Wheelchair Vermeiren'},

    {'parent_id': 3,    'name': 'Folding wheelchairs'},
    {'parent_id': 3,    'name': 'Wheelchair for disabled people'},
    {'parent_id': 3,    'name': 'Wheelchairs for transportation of patients'},

    {'parent_id': 4,    'name': 'Foldable scooters for disabled people'},
    {'parent_id': 4,    'name': 'Three-wheeled scooters for disabled people'}
]

for param in data:
    db.session.add(Category(**param))

db.session.commit()

查询:

top_query = db.session.query(Category, db.literal(0).label('level')) \
                    .filter(Category.parent_id == None) \
                    .cte(name='top_query', recursive=True)

top_query = db.aliased(top_query, name='my_category')

bottom_query = db.session.query(Category, (top_query.c.level + 1).label('level')) \
                        .join(top_query, Category.parent_id == top_query.c.id) \
                        .order_by(db.desc(Category.parent_id)) # !!!!!!!!!!!!!!!!!!!!!!

hierarchy_query = top_query.union_all(bottom_query)

db.session.query(hierarchy_query).all()

带括号的底部SELECT错误:

WITH RECURSIVE my_category(id, parent_id, name, level) AS (
    SELECT category.id AS id,
           category.parent_id AS parent_id,
           category.name AS name,
           ? AS level 
      FROM category 
     WHERE category.parent_id IS NULL

    UNION ALL

    (SELECT category.id AS category_id,
            category.parent_id AS category_parent_id,
            category.name AS category_name,
            my_category.level + ? AS level 
       FROM category JOIN my_category ON category.parent_id = my_category.id
    ORDER BY category.parent_id DESC)
)

SELECT my_category.id AS my_category_id,
       my_category.parent_id AS my_category_parent_id,
       my_category.name AS my_category_name,
       my_category.level AS my_category_level 
  FROM my_category

如果对order_by进行了注释,则不会引发异常:

top_query = db.session.query(Category, db.literal(0).label('level')) \
                    .filter(Category.parent_id == None) \
                    .cte(name='top_query', recursive=True)

top_query = db.aliased(top_query, name='my_category')

bottom_query = db.session.query(Category, (top_query.c.level + 1).label('level')) \
                        .join(top_query, Category.parent_id == top_query.c.id) \
                        # .order_by(db.desc(Category.parent_id))

hierarchy_query = top_query.union_all(bottom_query)

db.session.query(hierarchy_query).all()

没有括号(也没有order_by)的底部SELECT没有错误:

WITH RECURSIVE my_category(id, parent_id, name, level) AS (
    SELECT category.id AS id,
           category.parent_id AS parent_id,
           category.name AS name,
           ? AS level 
      FROM category 
     WHERE category.parent_id IS NULL

    UNION ALL

    SELECT category.id AS h_category_id,
           category.parent_id AS category_parent_id,
           category.name AS category_name,
           my_category.level + ? AS level
      FROM category JOIN my_category ON category.parent_id = my_category.id
)

SELECT my_category.id AS my_category_id,
       my_category.parent_id AS my_category_parent_id,
       my_category.name AS my_category_name,
       my_category.level AS my_category_level
  FROM my_category

1 个答案:

答案 0 :(得分:0)

SQLAlchemy之所以以这种方式编译查询,是因为

SELECT x FROM foo UNION ALL (SELECT x FROM bar ORDER BY x)

SELECT x FROM foo UNION ALL SELECT x FROM bar ORDER BY x;

是不同的查询(在支持第一查询的语法的数据库中,例如Postgresql):

  • 前者是2个选择之间的联合,其中第二个是有序的
  • 后者是2个无序选择之间的并集。复合选择是整体排序的。

SQLite不支持以前查询中使用的语法,因此语法错误。

WITH statement的SQLite实现中,将ORDER BY应用于整体选择的化合物具有以下含义:

  

如果存在ORDER BY子句,则它将确定步骤2a中从队列中提取行的顺序。 ...

因此,与其像对联盟中使用的第二个查询那样应用排序,不如将它应用于CTE中使用的复合选择,但是不幸的是,SQLAlchemy CTE似乎不支持开箱即用。您可以为此实现自己的功能:

from sqlalchemy.sql.selectable import CTE

# Following the implementation of CTE.union_all():
def cte_order_by(self, *clauses):
    return CTE(
        self.original.order_by(*clauses),
        name=self.name,
        recursive=self.recursive,
        _restates=self._restates.union([self]),
        _suffixes=self._suffixes
    )

,然后在查询中使用它:

hierarchy_query = top_query.union_all(bottom_query)
hierarchy_query = cte_order_by(hierarchy_query, db.desc('2'))