【问题标题】:Split SQL statements on function name but keep delimiter in Python在函数名称上拆分 SQL 语句,但在 Python 中保留分隔符
【发布时间】:2019-03-07 11:01:29
【问题描述】:

假设我有以下 string 包含从 SELECT 子句中提取的 SQL 语句(实际上这是一个包含数百个此类语句的巨大 SQL 语句);

  SUM(case when(A.money-B.money>1000
                and A.unixtime-B.unixtime<=890769
                and B.col10 = "A"
                and B.col11 = "12"
                and B.col12 = "V") then 10
      end) as finalCond0,
  MAX(case when(A.money-B.money<0
                and A.unixtime-B.unixtime<=6786000
                and B.cond1 = "A"
                and B.cond2 = "4321"
                and B.cond3 in ("E", "F", "G")) then A.col10
        end) as finalCond1,
  SUM(case when(A.money-B.money>0
                and A.unixtime-B.unixtime<=6786000
                and B.cond1 = "A"
                and B.cond2 = "1234"
                and B.cond3 in ("A", "B", "C")) then 2
      end) as finalCond2    

如何在函数上拆分此查询(即SUM、MAX、MIN、MEAN 等),以便我可以提取最后一个查询但不删除分隔符(在本例中为SUM)?

因此,所需的输出将是如下所示的字符串:

  SUM(case when(A.money-B.money>0
                and A.unixtime-B.unixtime<=6786000
                and B.cond1 = "A"
                and B.cond2 = "1234"
                and B.cond3 in ("A", "B", "C")) then 2
      end) as finalCond2

PS:出于演示目的,我提供了某种缩进,但实际上这些语句用逗号分隔,这意味着原始形式中不会出现空格或换行。

【问题讨论】:

  • 你试过用逗号(,)分割吗?
  • @Ralf 在这种情况下不起作用。拆分, (sql.split(',').pop()) 将得到"C")) then 2 end) as finalCond2
  • 嗯...和.split(',\n') ?
  • @Ralf 也不起作用。出于演示目的,我提供了某种缩进,但实际上这些语句只是用逗号分隔,这意味着原始形式中不会出现空格或换行。

标签: python sql regex split


【解决方案1】:

您不能在此处使用正则表达式,因为 SQL 语法不会形成您可以与 Python re 引擎匹配的正则模式。您实际上必须将字符串 parse 到令牌流或语法树中;毕竟,您的SUM(...) 可以包含多种语法,包括子选择。

sqlparse library 可以做到这一点,即使它是 bit underdocumented and not that friendly to external uses。

重新使用我在链接到的另一篇文章中定义的walk_tokens 函数:

from collections import deque
from sqlparse.sql import TokenList

def walk_tokens(token):
    queue = deque([token])
    while queue:
        token = queue.popleft()
        if isinstance(token, TokenList):
            queue.extend(token)
        yield token

从SELECT标识符列表中提取最后一个元素是:

import sqlparse
from sqlparse.sql import IdentifierList

tokens = sqlparse.parse(sql)[0]
for tok in walk_tokens(tokens):
    if isinstance(tok, IdentifierList):
        # iterate to leave the last assigned to `identifier`
        for identifier in tok.get_identifiers():
            pass
        break

print(identifier)

演示:

>>> sql = '''\
...   SUM(case when(A.money-B.money>1000
...                 and A.unixtime-B.unixtime<=890769
...                 and B.col10 = "A"
...                 and B.col11 = "12"
...                 and B.col12 = "V") then 10
...       end) as finalCond0,
...   MAX(case when(A.money-B.money<0
...                 and A.unixtime-B.unixtime<=6786000
...                 and B.cond1 = "A"
...                 and B.cond2 = "4321"
...                 and B.cond3 in ("E", "F", "G")) then A.col10
...         end) as finalCond1,
...   SUM(case when(A.money-B.money>0
...                 and A.unixtime-B.unixtime<=6786000
...                 and B.cond1 = "A"
...                 and B.cond2 = "1234"
...                 and B.cond3 in ("A", "B", "C")) then 2
...       end) as finalCond2
... '''
>>> tokens = sqlparse.parse(sql)[0]
>>> for tok in walk_tokens(tokens):
...     if isinstance(tok, IdentifierList):
...         # iterate to leave the last assigned to `identifier`
...         for identifier in tok.get_identifiers():
...             pass
...         break
...
>>> print(identifier)
SUM(case when(A.money-B.money>0
                and A.unixtime-B.unixtime<=6786000
                and B.cond1 = "A"
                and B.cond2 = "1234"
                and B.cond3 in ("A", "B", "C")) then 2
      end) as finalCond2

identifier 是一个sqlparse.sql.Identifier 实例,但再次将其转换为字符串(print() 会这样做,或者您可以只使用str())再次为您提供该部分的输入 SQL 字符串。

【讨论】:

  • 使用正则表达式会不会更容易?
  • @Old-School:不,因为 Python 的正则表达式不能用于解析嵌套结构。
  • @Old-School:SQL 不是'常规',你无法预测括号的数量,或者逗号将在哪里使用等等。
  • 即使我没有完整的 SQL 语句,这个解决方案也能工作吗?这意味着它们只是来自 SELECT 语句的标识符,而不是实际的语句本身。
  • @Old-School:我使用了你的文字输入,它在演示中。看不到SELECT 或FROM 或JOIN。
【解决方案2】:

我有一个解决方案,但是代码有点多。这没有使用regex,只是多次迭代关键字拆分。

s = """
SUM(case when(A.money-B.money>1000
                and A.unixtime-B.unixtime<=890769
                and B.col10 = "A"
                and B.col11 = "12"
                and B.col12 = "V") then 10
      end) as finalCond0,
MAX(case when(A.money-B.money<0
                and A.unixtime-B.unixtime<=6786000
                and B.cond1 = "A"
                and B.cond2 = "4321"
                and B.cond3 in ("E", "F", "G")) then A.col10
      end) as finalCond1,
SUM(case when(A.money-B.money>0
                and A.unixtime-B.unixtime<=6786000
                and B.cond1 = "A"
                and B.cond2 = "1234"
                and B.cond3 in ("A", "B", "C")) then 2
      end) as finalCond2 
"""

# remove newlines and doble spaces
s = s.replace('\n', ' ')
while '  ' in s:
    s = s.replace('  ', ' ')
s = s.strip()

# split on keywords, starting with the original string
current_parts = [s, ]
for kw in ['SUM', 'MAX', 'MIN']:
    new_parts = []
    for part in current_parts:
        for i, new_part in enumerate(part.split(kw)):
            if i > 0:
                # add keyword to the start of this substring
                new_part = '{}{}'.format(kw, new_part)

            new_part = new_part.strip()
            if len(new_part) > 0:
                new_parts.append(new_part.strip())

    current_parts = new_parts

print()
print('current_parts:')
for s in current_parts:
    print(s)

我得到的输出是:

current_parts:
SUM(case when(A.money-B.money>1000 and A.unixtime-B.unixtime<=890769 and B.col10 = "A" and B.col11 = "12" and B.col12 = "V") then 10 end) as finalCond0,
MAX(case when(A.money-B.money<0 and A.unixtime-B.unixtime<=6786000 and B.cond1 = "A" and B.cond2 = "4321" and B.cond3 in ("E", "F", "G")) then A.col10 end) as finalCond1,
SUM(case when(A.money-B.money>0 and A.unixtime-B.unixtime<=6786000 and B.cond1 = "A" and B.cond2 = "1234" and B.cond3 in ("A", "B", "C")) then 2 end) as finalCond2

它对你有用吗?它似乎适用于您在问题中输入的示例字符串。

【讨论】:

  • 但是我已经告诉过你,字符串中没有新行(\n)。
  • 无论字符串中是否有换行符,我发布的代码都可以使用。我只是确保开头没有换行符,但如果字符串中没有任何换行符,这不会影响结果。
  • 好吧好吧。感谢您的尝试。
【解决方案3】:

你可以使用类似的东西:

import re

str = 'SUM(case when(A.money-B.money>1000 and A.unixtime-B.unixtime<=890769 and B.col10 = "A" and B.col11 = "12" and B.col12 = "V") then 10 end) as finalCond0, MAX(case when(A.money-B.money<0 and A.unixtime-B.unixtime<=6786000 and B.cond1 = "A" and B.cond2 = "4321" and B.cond3 in ("E", "F", "G")) then A.col10 end) as finalCond1, SUM(case when(A.money-B.money>0 and A.unixtime-B.unixtime<=6786000 and B.cond1 = "A" and B.cond2 = "1234" and B.cond3 in ("A", "B", "C")) then 2 end) as finalCond2'

result = re.finditer('as\s+[a-zA-Z0-9]+', str);

commas = []
parts = []

for reg in result:
    end = reg.end()
    if(len(str) > end and str[end] == ','):
        commas.append(end)

idx = 0
for comma in commas:
    parts.append(str[idx:comma])
    idx = comma + 1
parts.append(str[idx:])

print(parts)

在commas 数组中,您将拥有需要拆分的逗号。输出将是:

[151, 322]

在部件中,您将拥有带有部件的最终数组(不确定此实现是否是最佳方式):

[
    'SUM(case when(A.money-B.money>1000 and A.unixtime-B.unixtime<=890769 and B.col10 = "A" and B.col11 = "12" and B.col12 = "V") then 10 end) as finalCond0',
    ' MAX(case when(A.money-B.money<0 and A.unixtime-B.unixtime<=6786000 and B.cond1 = "A" and B.cond2 = "4321" and B.cond3 in ("E", "F", "G")) then A.col10 end) as finalCond1',
    ' SUM(case when(A.money-B.money>0 and A.unixtime-B.unixtime<=6786000 and B.cond1 = "A" and B.cond2 = "1234" and B.cond3 in ("A", "B", "C")) then 2 end) as finalCond2'
]

【讨论】:

  • 此代码的输出是否接近我在问题中发布的所需输出?
  • 为您编辑,立即查看!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-14
  • 2018-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多