Python SQLクエリ文字列のフォーマット


93

SQLクエリ文字列をフォーマットするための最良の方法を見つけようとしています。アプリケーションをデバッグしているとき、すべてのSQLクエリ文字列をログに記録したいのですが、文字列が適切にフォーマットされていることが重要です。

オプション1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • これは、SQL文字列を出力するのに適しています。
  • 文字列が長く、標準の80文字の幅に収まらない場合は、適切な解決策ではありません。

オプション2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • ここではコードは明確ですが、sqlクエリ文字列を出力すると、これらの迷惑な空白がすべて表示されます。

    u '\ nselect field1、field2、field3、field4 \ n_ _ ___ テーブルから\ n _ ___ where condition1 = 1 \ n _ ___ and condition2 = 2'

注:空白_はエディターによって削除されるため、空白をアンダースコアに置き換えました。

オプション3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • このオプションは、適切に集計されたコードの明確さを損なうので、好きではありません。

オプション4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • 各行に余分に入力する必要があり、クエリの編集も難しいため、このオプションは好きではありません。

私にとって最良の解決策はオプション2ですが、sql文字列を出力するときの余分な空白文字は好きではありません。

他のオプションを知っていますか?


これは、Psycopgの人々がクエリ文字列の構成にナイーブなアプローチと呼んでいるものです。たとえば、文字列連結-initd.org/psycopg/docs/…を使用します。代わりにクエリパラメータを使用して、SQLインジェクション攻撃を回避し、PythonオブジェクトをSQLリテラルとの間で自動的に変換します。stackoverflow.com/questions/3134691/…–
マシューコーネル

この質問は実際にはSQLクエリに固有のものではありませんが、Pythonでの複数行の文字列のフォーマットに一般的に当てはまります。SQLタグを削除する必要があります。
cstork 2014年

回答:


130

そのような古いスレッドに投稿して申し訳ありません-しかし、pythonicの「最高」への情熱を共有する誰かとして、私は私たちのソリューションを共有したいと思いました。

解決策は、Pythonの文字列リテラル連結(http://docs.python.org/)を使用してSQLステートメントを構築することです。これは、オプション2とオプション4の間のどこかで修飾できます。

コードサンプル:

sql = ("SELECT field1, field2, field3, field4 "
       "FROM table "
       "WHERE condition1=1 "
       "AND condition2=2;")

f-stringsでも動作します

fields = "field1, field2, field3, field4"
table = "table"
conditions = "condition1=1 AND condition2=2"

sql = (f"SELECT {fields} "
       f"FROM {table} "
       f"WHERE {conditions};")

長所:

  1. それはpythonicの「よく表化された」フォーマットを保持しますが、余分なスペース文字を追加しません(ロギングを汚染します)。
  2. オプション4のバックスラッシュ継続の醜さを回避し、ステートメントの追加を困難にします(空白の盲目は言うまでもありません)。
  3. さらに、VIMでステートメントを展開するのは非常に簡単です(カーソルを挿入ポイントに置き、SHIFT-Oを押して新しい行を開くだけです)。

1
これが印刷用である場合、出力前にmutiline文字列として書き込み"""、使用することをお勧めしますtextwrap.dedent()
slezica

そのオプションを試してみましたが、ログ出力も複数行になりました。db chattyアプリを追跡すると、大量の出力が発生しました。
user590028 14

1
これは古いスレッドですが、私はこの形式をベストプラクティスとして使用してきましたが、クエリが長くなると面倒になります
Jabda

7
"sql query"SQL文字列(標準として一重引用符を使用します)の混乱を避けるために、常に二重引用符を使用すべきではありませんか?
tpvasconcelos

19

SQLを出力するように多くの方法でSQLを書き込む方法を検討したことは明らかですが、デバッグログに使用する「print」ステートメントを、好きではない方法でSQLを書き込むのではなく変更してみませんか?上記のお気に入りのオプションを使用して、次のようなロギング機能はどうでしょうか。

def debugLogSQL(sql):
     print ' '.join([line.strip() for line in sql.splitlines()]).strip()

sql = """
    select field1, field2, field3, field4
    from table"""
if debug:
    debugLogSQL(sql)

これにより、行が目的の長さよりも長い場合に、ログに記録された文字列を複数の行に分割するロジックを追加することも簡単になります。


11

私が出会った最もクリーンな方法は、SQLスタイルガイドに触発されました。

sql = """
    SELECT field1, field2, field3, field4
      FROM table
     WHERE condition1 = 1
       AND condition2 = 2;
"""

基本的に、句を開始するキーワードは右揃えにし、フィールド名などは左揃えにする必要があります。これは非常に見栄えがよく、デバッグも簡単です。


2
sql = ("select field1, field2, field3, field4 "
       "from table "
       "where condition1={} "
       "and condition2={}").format(1, 2)

Output: 'select field1, field2, field3, field4 from table 
         where condition1=1 and condition2=2'

条件の値が文字列である必要がある場合は、次のようにすることができます。

sql = ("select field1, field2, field3, field4 "
       "from table "
       "where condition1='{0}' "
       "and condition2='{1}'").format('2016-10-12', '2017-10-12')

Output: "select field1, field2, field3, field4 from table where
         condition1='2016-10-12' and condition2='2017-10-12'"

5
これを絶対にしないでください。これはSQLインジェクションと呼ばれ、本当に危険です。ほとんどすべてのPythonデータベースライブラリは、パラメータを使用するための機能を提供します。format()SQL文字列を使用していることに気付いた場合、それは主要なコードのにおいです。
mattmc3

私たちはそれを使用できないとは思いません。使用する前にパラメーターを検証する必要があり、渡したものを知っている必要があります。
pangpang

検証はwhere condition1=:field1、値をパラメーターとして使用して渡すよりもはるかに多くのエラーが発生しやすくなります。を使用している場合、SQLに.format()aをポップする方法があり';DROP TABLE Usersます。パラメータを正しく使用する方法については、PEP-249を参照してください。python.org/dev/peps/pep-0249/#paramstyle
mattmc3

0

完全フォーマットすることを避けるため、私はすばらしい解決策は手続きを使うことだと思います

プロシージャを呼び出すと、このプロシージャに入れたいクエリ結果が得られます。プロシージャ内で複数のクエリを実際に処理できます。呼び出しは、呼び出された最後のクエリを返すだけです。

MYSQL

DROP PROCEDURE IF EXISTS example;
 DELIMITER //
 CREATE PROCEDURE example()
   BEGIN
   SELECT 2+222+2222+222+222+2222+2222 AS this_is_a_really_long_string_test;
   END //
 DELIMITER;

#calling the procedure gives you the result of whatever query you want to put in this procedure. You can actually process multiple queries within a procedure. The call just returns the last query result
 call example;

パイソン

sql =('call example;')

-1

フィールド名を配列「フィールド」に入れて、次のようにすることができます。


sql = 'select %s from table where condition1=1 and condition2=2' % (
 ', '.join(fields))

条件リストが大きくなった場合も、 'および' .join(conditions)を使用して同じことができます
jcomeau_ictx

ソリューションでは、クエリはOption_4を使用する場合よりもさらに編集が難しくなり、読み取りも難しくなります。
ssoler 2011年

@ssoler、それは人のやり方に依存します。私はプログラムでいくつかの変数を宣言し、代わりに文字列の配列を使用します。これにより、少なくとも私は、上記のようなメソッドを非常に便利保守可能にします。
jcomeau_ictx


-1

1行または2行に収まる短いクエリの場合は、上記の上位投票ソリューションで文字列リテラルソリューションを使用します。より長いクエリの場合は、.sqlファイルに分割します。次に、ラッパー関数を使用してファイルを読み込み、スクリプトを実行します。

script_cache = {}
def execute_script(cursor,script,*args,**kwargs):
    if not script in script_cache:
        with open(script,'r') as s:
            script_cache[script] = s
    return cursor.execute(script_cache[script],*args,**kwargs)

もちろん、これはクラス内にあることが多いので、通常、cursor明示的に渡す必要はありません。私も一般的にを使用しますcodecs.open()が、これは全体的な考え方を理解します。次に、SQLスクリプトは、独自の構文強調表示を備えた独自のファイルに完全に自己完結します。


-2
sql = """\
select field1, field2, field3, field4
from table
where condition1=1
and condition2=2
"""

[コメントに編集]
SQL文字列がメソッド内にあるからといって、それを「表にする」必要があるわけではありません。

>>> class Foo:
...     def fubar(self):
...         sql = """\
... select *
... from frobozz
... where zorkmids > 10
... ;"""
...         print sql
...
>>> Foo().fubar()
select *
from frobozz
where zorkmids > 10
;
>>>

IMOこれはOption_2と同じです
ssoler

@ssoler:Option_2のすべての行に先行スペースがあります。この例では、前の先行スペースを省略していることに注意してくださいselect。私の答えには先行スペースがありません。それらが同じであるという意見を形成するようになったのは何ですか?
John Machin、2011年

SQL文字列をメソッド内に配置する場合は、すべての行を表にまとめる必要があります(Option_2)。これに対する可能な解決策の1つはOption_3です。
ssoler 2011年

@ssoler:すみません、私はその発言を理解していません。私の更新された答えを見てください。
John Machin

あなたの更新された答えは私のOption_3ですね。このオプションは、適切に集計されたコードの明確さを損なうため、私は好きではありません。
ssoler 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.