如何在SQLAlchemy中漂亮地格式化打印SQL查询

34

我正在使用SQLAlchemy通过compile来动态生成PL/SQL,同时设置dialectcompile_kwargs参数(例如,使用str(ins.compile(dialect=oracle.dialect(), compile_kwargs={'literal_binds': True})))。这样做运行得很好,但是输出的SQL语句不是最漂亮的。

例如,我的其中一个输出如下:

INSERT INTO my_table (a, b, c) SELECT my_table2.d, bar.e, bar.f
FROM my_table2 JOIN (SELECT my_table3.e AS e, max(my_table3.f) AS f, count(my_table3.g) AS g
FROM my_table3
WHERE my_table3.h = 'foo' GROUP BY my_table3.e
HAVING count(my_table3.g) = 1) bar ON my_table2.g = bar.g

相反,我希望它打印出以下内容:

INSERT INTO my _table (a, b c)
SELECT my_table2.d, bar.e, bar.f
FROM my_table2 JOIN (
    SELECT my_table3.e, max(my_table3.f), count(my_table3.g)
    FROM my_table3
    WHERE my_table3.h = 'foo'
    GROUP BY my_table3.e
    HAVING count(my_table3.g) = 1
) bar ON my_table2.g = bar.g

如何让SQLAlchemy以漂亮的方式打印SQL语句?

复制以下代码:

from sqlalchemy import table, column, String, Numeric, func, select
from sqlalchemy.dialects import oracle
my_table = table('my_table', column('a', String), column('b', String), column('c', String))
my_table2 = table('my_table2', column('d', String), column('g', String))
my_table3 = table('my_table3', column('d', String), column('e', String), column('f', Numeric), column('g', String), column('h', String))
inner_sel = select([my_table3.c.e, func.max(my_table3.c.f).label('f'), func.count(my_table3.c.g).label('g')]).where(my_table3.c.h=='foo').group_by(my_table3.c.e).having(func.count(my_table3.c.g)==1).alias('bar')
outer_sel = select([my_table2.c.d, inner_sel.c.e, inner_sel.c.f]).select_from(my_table2.join(inner_sel, my_table2.c.g==inner_sel.c.g))
ins = my_table.insert().from_select([my_table.c.a, my_table.c.b, my_table.c.c], outer_sel) print ins.compile(dialect=oracle.dialect(), compile_kwargs={'literal_binds': True})

2
我认为这不是开箱即用的。尝试使用 sqlparse - univerio
似乎它仍在尝试着做某些事情:它不是全部在一行上,而是在SQL关键字之前有一些换行...嗯,我也在寻找一些免费且智能的工具来完成这个任务。 - Tomasz Gandor
5个回答

34
你可以使用 sqlparse 包和 sqlparse.format(sql, reindent=True, keyword_case='upper')。它应该可以做到你想要的。

很好的例子。谢谢。 - Cerin
在我看来,shandy-sqlfmt(请参见http://sqlfmt.com/)更快更漂亮。相应的API命令是`format_string(sql, Mode())`。 - undefined

18

项目 sqlparse 已经成熟(超过10年),并仍然非常活跃。 sqlparse 的目标是解析、分割和格式化SQL语句。

下面的例子使用 sqlparse 来美化格式化SQL文件:

import argparse
import sqlparse

# Parse command line arguments
parser = argparse.ArgumentParser(prog="pretty_print_sql")
parser.add_argument("file", type=argparse.FileType("r"), nargs="+")
args = parser.parse_args()

# Pretty print input files
for file in args.file:
    print(sqlparse.format(file.read(), reindent=True, keyword_case='upper'))

使用pip安装sqlparse用于个人使用:

python3 -m pip install sqlparse --user --upgrade

使用 pipenv(在项目中)安装 sqlparse

python3 -m pipenv install sqlparse

10

请注意,这三个库中只有 sqlparse 正在积极维护。 - Wojciech Walczak

3

尝试使用这个猴子补丁:

pip install sqlparse

### monkeypatching SQL'Alchemy for pretty SQL query printing (((
from sqlalchemy.log import InstanceLogger


def pretty_log(self, level, msg, *args, **kwargs):
    if self.logger.manager.disable >= level:
        return

    selected_level = self._echo_map[self.echo]
    if selected_level == logging.NOTSET:
        selected_level = self.logger.getEffectiveLevel()

    if level >= selected_level:

        import sqlparse
        ### HERE IT IS ###
        msg = sqlparse.format(msg, reindent=True, keyword_case='upper')

        self.logger._log(level, '\n'+msg, args, **kwargs)

InstanceLogger.log = pretty_log
### )))

0

参考了 v_retouxoHo 的示例,我在 GitHub 上创建了 一个易于部署的脚本,使用了 sqlparse 库。它可以处理一个或多个 SQL 文件,并生成干净的输出结果,对于单个文件还可以进行管道传输。

以下是源代码:

import argparse, sqlparse, re

parser = argparse.ArgumentParser(prog="sqlpp")
parser.add_argument("--verbose", "-v", action='store_true')
parser.add_argument("file", type=argparse.FileType("r"), nargs="+")

args = parser.parse_args()

def prepend(s, s2): return s2 + re.sub('\n', '\n'+s2, s)

# Pretty print input files
n=len(args.file)
for i, file in enumerate(args.file):
    sIn = file.read().replace('\n', '')
    file.close()
    sOut = sqlparse.format(sIn, reindent=True, keyword_case='upper')
    if args.verbose or n > 1:
        print("File{0}:\n    {1}\n{2}\nFormatted SQL:\n{3}\n".format(
             (' ' + str(i+1) if n > 1 else '')
            ,file.name
            ,("\nOriginal SQL:\n{}\n".format(prepend(sIn, "    "))
                    if args.verbose else "")
            ,prepend(sOut, "    ")
        ))
    else:
        print(sOut)

1
是的,我之前对是否添加这段代码犹豫不决。现在我会把主要逻辑加入到主体中。 - Timothy C. Quinn
这里没有叫“olibre”的人。它是指Alan还是oHo'a的回答? - Peter Mortensen
知道了。一定是oHo的。我会更新我的答案。 - Timothy C. Quinn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接