Python 使用sqlparse时解析语句的大小写

Python 使用sqlparse时解析语句的大小写,python,sql-parser,Python,Sql Parser,我有以下SQL查询,并希望使用 导入sqlparse query=”“” 选择SUM(当(A.dt_)为0时的大小写 和B.Cat1=“A” 和(B.Cat2=“M” 或B.Cat3=“C” 或B.Cat4=“B”) B.Cat5为空)则为1 结束)作为测试列2 从测试表A A.ID=B.ID上作为B的左连接测试表2 其中A.DT>B.DT 按A.ID分组 """ query\u tokens=sqlparse.parse(查询)[0]。tokens 打印(查询令牌) 将给出SQL语句中包含的

我有以下SQL查询,并希望使用

导入sqlparse query=”“” 选择SUM(当(A.dt_)为0时的大小写 和B.Cat1=“A” 和(B.Cat2=“M” 或B.Cat3=“C” 或B.Cat4=“B”) B.Cat5为空)则为1 结束)作为测试列2 从测试表A A.ID=B.ID上作为B的左连接测试表2 其中A.DT>B.DT 按A.ID分组 """ query\u tokens=sqlparse.parse(查询)[0]。tokens 打印(查询令牌) 将给出SQL语句中包含的所有标记:

[<Newline ' ' at 0x7FAA62BD9F48>, <DML 'select' at 0x7FAA62BE7288>, <Whitespace ' ' at 0x7FAA62BE72E8>, <IdentifierList 'SUM(ca...' at 0x7FAA62BF7CF0>, <Newline ' ' at 0x7FAA62BF6288>, <Keyword 'from' at 0x7FAA62BF62E8>, <Whitespace ' ' at 0x7FAA62BF6348>, <Identifier 'test_t...' at 0x7FAA62BF7570>, <Newline ' ' at 0x7FAA62BF64C8>, <Keyword 'left j...' at 0x7FAA62BF6528>, <Whitespace ' ' at 0x7FAA62BF6588>, <Identifier 'test_t...' at 0x7FAA62BF7660>, <Whitespace ' ' at 0x7FAA62BF67C8>, <Keyword 'on' at 0x7FAA62BF6828>, <Whitespace ' ' at 0x7FAA62BF6888>, <Comparison 'A.ID=B...' at 0x7FAA62BF7B10>, <Newline ' ' at 0x7FAA62BF6B88>, <Where 'where ...' at 0x7FAA62BF28B8>, <Keyword 'group' at 0x7FAA62BD9E88>, <Whitespace ' ' at 0x7FAA62BD93A8>, <Keyword 'by' at 0x7FAA62BD9EE8>, <Whitespace ' ' at 0x7FAA62C1CEE8>, <Identifier 'A.ID' at 0x7FAA62BF2F48>, <Newline ' ' at 0x7FAA62BF6C48>]
[,,,,,,,,,]
如何解析这些标记以处理
CASE WHEN
语句,从而提取所有条件并保持它们的优先级,如使用括号所定义的那样。我在文档中找不到任何相关的例子


对此有什么想法吗?

这个项目确实有点文档不足。我看了一下,浏览了一下源代码。遗憾的是,文档没有包括
Token
TokenList
类中对该任务有用的所有方法

例如,一个重要但被忽略的方法是,它使您比其他方法更容易遍历嵌套的令牌列表;该方法只在树中生成未分组的标记,而
CASE
是一个分组的标记,因此单纯地看文档,您可能会发现很难对解析的标记树做一些有用的事情

我在代码库中注意到的另一个方便的方法是,它将当前的令牌树转储到stdout。这在尝试编写分析树的代码时非常有用

总之,我对
sqlparse
的总体印象是,与其说它是一个解析库,不如说它是一个重新格式化SQL的工具。它包括一个好的解析器,但不包括一般使用它生成的树所需的工具

库中真正缺少的是一个基本节点访问者类,例如由提供的,或者一个树节点遍历器,同样类似于。幸运的是,这两种方法都很容易自己构建:

from collections import deque
from sqlparse.sql import TokenList

class SQLTokenVisitor:
    def visit(self, token):
        """Visit a token."""
        method = 'visit_' + type(token).__name__
        visitor = getattr(self, method, self.generic_visit)
        return visitor(token)

    def generic_visit(self, token):
        """Called if no explicit visitor function exists for a node."""
        if not isinstance(token, TokenList):
            return
        for tok in token:
            self.visit(tok)

def walk_tokens(token):
    queue = deque([token])
    while queue:
        token = queue.popleft()
        if isinstance(token, TokenList):
            queue.extend(token)
        yield token
现在,您可以使用以下任一选项访问
案例
节点:

statement, = sqlparse.parse(query)

class CaseVisitor(SQLTokenVisitor):
    """Build a list of SQL Case nodes

      The .cases list is a list of (condition, value) tuples per CASE statement

    """
    def __init__(self):
        self.cases = []

    def visit_Case(self, token):
        branches = []
        for when, then_ in token.get_cases():
            branches
        self.cases.append(token.get_cases())

visitor = CaseVisitor()
visitor.visit(statement)
cases = visitor.cases

在本例中,
walk_tokens()
NodeVisitor
模式之间的差异可以忽略不计,但我们只是为每个
CASE
语句提取分离的标记,而不处理
当。。。然后…
tokens。在
NodeVisitor
模式中,您将当前访问者实例上的更多属性设置为“切换设备”,并在更多
visit...
方法中捕获关于这些子树标记的更多信息,这可能比在生成器上嵌套
更容易遵循

另一方面,使用
walk_tokens()
生成器,如果创建一个单独的变量来引用生成器,则可以将迭代交给助手函数:

all_tokens = walk_tokens(stamement)
for token in walk_tokens(statement):
    if isinstance(token, sqlparse.sql.Case):
        branches = extract_branches(all_tokens)

其中,
extract\u分支
将进一步迭代,直到case语句结束。

写了一个答案后,我现在看一看,这说明sqlparse不提供树,而是一个令牌列表。。虽然该语句并不完全准确,
sqlparse
并不能使令牌树的使用变得简单。@MartijnPieters我看过mozzila的项目,但当解析更复杂的SQL语句时,它会引发递归异常。Crumbs,
moz sql parser
当被要求解析示例语句时,实际上会引发递归异常。@MartijnPieters完全正确。它已经是结构化数据,只需遍历字典和列表,可能还有一个堆栈。
statement, = sqlparse.parse(query)

cases = []
for token in walk_tokens(statement):
    if isinstance(token, sqlparse.sql.Case):
        cases.append(token.get_cases())
all_tokens = walk_tokens(stamement)
for token in walk_tokens(statement):
    if isinstance(token, sqlparse.sql.Case):
        branches = extract_branches(all_tokens)