解析 SQL 子查询的简单方法

Simple way to parse SQL subqueries

我正在开发一个 SQL 分析工具,给定一个 RAW SQL SELECT 查询,它可以提供某种分析。该工具的第一个版本已经完成,可以分析简单的 RAW 查询。但是,当查询包含子查询时,它会中断。

所以我正在寻找一种简单但可靠的方法来解析查询和子查询。我的工具必须单独分析每个子查询,例如:

假设这是该工具作为输入给出的查询:

SELECT name, email
FROM (SELECT * FROM user WHERE email IS NOT NULL)
WHERE id IN (SELECT cID FROM customer WHERE points > 5)

然后我想得到一个这样的查询列表:

queries = [
    "SELECT name, EMAIL FROM <subquery> WHERE id in <subquery>"
    "SELECT * FROM user WHERE email IS NOT NULL"
    "SELECT cID FROM customer WHERE points > 5)"
]

在我的第一次尝试中,我使用了子查询总是写在括号之间的事实。所以我扫描括号的初始查询。这在子查询未嵌套时有效,即子查询内没有子查询。我也尝试了一下 AST,但觉得它可能有点太复杂了,可能还有更简单的方法。

谁能指导我正确的方向?我正在使用 Python,但也非常感谢其他语言的示例。

您可以使用 sqlparse:

import sqlparse
def queries(d):
  if type(d) != sqlparse.sql.Token:
     paren = isinstance(d, sqlparse.sql.Parenthesis)
     v = [queries(i) for i in (d if not paren else d[1:-1])]
     subseq, qrs = ''.join(str(i[0]) for i in v), [x for _, y in v for x in y]
     if [*d][paren].value == 'SELECT':
        return '<subquery>', [subseq]+qrs
     return subseq, qrs
  return d, []

s="""SELECT name, email
     FROM (SELECT * FROM user WHERE email IS NOT NULL)
     WHERE id IN (SELECT cID FROM customer WHERE points > 5)
"""
_, subqueries = queries(sqlparse.parse(s)[0])

输出:

['SELECT name, email\n     FROM <subquery>\n     WHERE id IN <subquery>\n', 'SELECT * FROM user WHERE email IS NOT NULL', 'SELECT cID FROM customer WHERE points > 5']

使用 sqlparse 库,您可以将 SQL 输入字符串解析为关键字、语句和值的标记化流。上面的函数 queries 接受一个 sqlparse.sql.Statement 对象并搜索查询中任何出现的 SELECT 语句,根据所需的输出样本重新格式化原始输入以删除子查询.