【问题标题】:python regular expression to extract and replace query parameters - cx_oracle requirementpython正则表达式提取和替换查询参数-cx_oracle要求
【发布时间】:2017-09-01 18:32:26
【问题描述】:

如果在 where 语句中传递字符串,python 模块 cx_Oracle 要求对查询进行参数化。我正在从文件中读取查询,这些查询看起来与您从 sql developer 之类的 IDE 中执行它们的方式完全相同。

示例查询

select name, count(1) from employees where status = 'Active' and role= 'Manager' group by name order by 1;

我想编写一些函数,将此查询作为输入,然后将参数作为元组输出:

query = 'select name, count(1) from employees where status = :1 and role= :2 group by name order by 1;'
parms = ('Active','Manager')

这样我可以在一个简单的函数中传递这两个来执行查询:

cursor_object.execute(query,parms)

不幸的是,我在正则表达式方面非常糟糕,我已经尝试了好几个小时,但无济于事。

【问题讨论】:

    标签: python sql regex cx-oracle


    【解决方案1】:

    给你:

    import re
    
    sql = """select name, count(1) from employees where status = 'Active' and role= 'Manager' group by name order by 1;"""
    
    rx = re.compile(r"""\w+\s*=\s*'([^']+)'""")
    params = rx.findall(sql)
    print(params)
    # ['Active', 'Manager']
    

    主要部分是

    \w+\s*=\s*'([^']+)'
    

    分解,这说:

    \w+\s*    # 1+ word characters, 0+ whitespace characters
    =\s*      # =, 0+ whitespace characters
    '([^']+)' # '(...)' -> group 1
    

    a demo on regex101.com


    要同时拥有查询和参数,您可以编写一个小函数:
    import re
    
    sql = """select name, count(1) from employees where status = 'Active' and role= 'Manager' group by name order by 1;"""
    
    rx = re.compile(r"""(\w+\s*=\s*)'([^']+)'""")
    
    def replacer(match):
        replacer.params.append(match.group(2))
        return '{}:{}'.format(match.group(1), len(replacer.params))
    
    replacer.params = list()
    query = rx.sub(replacer, sql)
    params = replacer.params
    
    print(query)
    print(params)
    # select name, count(1) from employees where status = :1 and role= :2 group by name order by 1;
    # ['Active', 'Manager']
    

    如 cmets 中所述,您需要为要分析的每个查询重置参数列表。

    【讨论】:

    • 感谢您抽出额外的时间来解释表达式。您对演示的分解和链接无疑帮助我理解了表达式是如何构建的。
    • 快速跟进问题:“替换器”函数如何在完全定义之前引用自身?我听说过自引用函数,但从未见过实际使用的函数。
    • @sikrut:在Python 中,函数和其他所有东西一样都是对象,因此您可以添加像params 这样的属性,它本身可以是字符串、列表甚至是另一个函数。
    • @Jan :如果你多次调用replacer函数,你会累积所有调用的参数。您需要在每次调用之前重置列表:replacer.params = []...
    • @LaurentLAPORTE:这绝对是需要注意的事情,是的。我已经编辑了答案。
    【解决方案2】:

    一个快速而肮脏的解决方案是编写一个匹配引用字符串的正则表达式。你可以这样开始:

    import re
    import textwrap
    
    query = textwrap.dedent("""\
    select name, count(1)
    from employees
    where status = 'Active' and role= 'Manager'
    group by name order by 1;""")
    
    sub_var = re.compile(r"'[^']+'").sub
    
    print(sub_var("VAR", query))
    # select name, count(1)
    # from employees
    # where status = VAR and role= VAR
    # group by name order by 1;
    

    但是,这里你需要用一个值来替换,每次匹配都会增加自己。

    为此,您需要一个函数。请记住,re.sub 可以将可调用对象作为替换项。可调用对象必须将 MatchObject 作为参数并返回替换。

    在这里,我更喜欢使用可调用的类:

    class CountVar(object):
        def __init__(self):
            self.count = 0
    
        def __call__(self, mo):
            self.count += 1
            return ":{0}".format(self.count)
    
    
    print(sub_var(CountVar(), query))
    # select name, count(1)
    # from employees
    # where status = :1 and role= :2
    # group by name order by 1;
    

    来了!

    【讨论】:

    • 感谢您提供此解决方案。有趣的是看到多个表达式版本。为什么使用类而不是函数的函数?我的意思可能是更根本的,因为我真的只在我的宠物项目中使用过函数。 没有太多经验的自学
    【解决方案3】:

    Jan 的答案的唯一问题是它不会生成您想要的带有“:1”、“:2”等的字符串。

    类似下面的东西应该可以工作:

    import re
    i=1
    pattern = r"(?<==\s*)'\w+'"
    params = []
    while True:
        match = re.find( pattern, cmd )
        if match is None: break
        params.append(match.group())
        cmd = re.sub( pattern, ":" + str(i), 1 )
        i += 1
    

    在该模式中,(?&lt;=) 被称为正向回溯,并确保参数(在本例中为 =\s*,后跟任意数量的空格的等号)出现在匹配的部分之前,但它是不包含在匹配中(因此它不会包含在params 中或在替换中被替换)。

    【讨论】:

    • 很高兴看到这是在一个循环中完成的,以及一个额外的表达式。没有意识到它们如此灵活,但也许这也让它们有点难以上手。
    猜你喜欢
    • 2016-03-24
    • 1970-01-01
    • 2014-01-12
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    相关资源
    最近更新 更多