Formatação de string de consulta SQL do Python


93

Estou tentando encontrar a melhor maneira de formatar uma string de consulta sql. Quando estou depurando meu aplicativo, gostaria de registrar para arquivar todas as strings de consulta sql, e é importante que a string esteja formatada corretamente.

Opção 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • Isso é bom para imprimir a string sql.
  • Não é uma boa solução se a string for longa e não se ajustar à largura padrão de 80 caracteres.

opção 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Aqui, o código é claro, mas quando você imprime a string de consulta sql, obtém todos esses espaços em branco irritantes.

    u '\ nselecione campo1, campo2, campo3, campo4 \ ​​n_ _ ___ da tabela \ n _ ___ onde condição1 = 1 \ n _ ___ _e condição2 = 2'

Observação: substituí os espaços em branco por sublinhados _, porque eles são cortados pelo editor

Opção 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Não gosto dessa opção porque quebra a clareza do código bem tabulado.

Opção 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • Não gosto desta opção porque toda a digitação extra em cada linha e é difícil editar a consulta também.

Para mim, a melhor solução seria a Opção 2, mas não gosto dos espaços em branco extras quando imprimo a string sql.

Você conhece outras opções?


Isso é o que as pessoas do Psycopg chamam de abordagem ingênua para a composição de strings de consulta, por exemplo, usando concatenação de string - initd.org/psycopg/docs/… . Em vez disso, use parâmetros de consulta para evitar ataques de injeção de SQL e para converter automaticamente objetos Python de e para literais SQL. stackoverflow.com/questions/3134691/…
Matthew Cornell

Na verdade, essa questão não é específica para consultas SQL, mas se aplica geralmente à formatação de strings de várias linhas em Python. A tag SQL deve ser removida.
cstork

Respostas:


130

Desculpe por postar em um tópico tão antigo - mas como alguém que também compartilha a paixão pelo 'melhor' pythônico, pensei em compartilhar nossa solução.

A solução é construir instruções SQL usando String Literal Concatenation do python ( http://docs.python.org/ ), que pode ser qualificada em algum lugar entre a Opção 2 e a Opção 4

Amostra de código:

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

Funciona bem com 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};")

Prós:

  1. Ele retém o formato pitônico 'bem tabulado', mas não adiciona caracteres de espaço estranhos (o que polui o registro).
  2. Isso evita a feiura da continuação da barra invertida da Opção 4, o que torna difícil adicionar afirmações (para não mencionar a cegueira do espaço em branco).
  3. Além disso, é realmente simples expandir a instrução no VIM (apenas posicione o cursor no ponto de inserção e pressione SHIFT-O para abrir uma nova linha).

1
Se for para impressão, acho que a melhor alternativa é escrever como string mutiline com """e usar textwrap.dedent()antes de
imprimir

Joguei com essa opção, mas tornou a saída do log de várias linhas também. Ao rastrear um aplicativo db chatty, isso causou uma saída volumosa.
user590028

1
Este é um tópico antigo, mas tenho usado esse formato como uma prática recomendada, no entanto, fica tedioso com consultas mais longas
Jabda

7
Não deveríamos sempre usar aspas duplas "sql query"para evitar mexer com strings SQL (que usam aspas simples como padrão)?
tpvasconcelos

19

Obviamente, você considerou muitas maneiras de escrever o SQL de forma que ele imprima bem, mas que tal mudar a instrução 'print' que você usa para o registro de depuração, em vez de escrever seu SQL de maneiras que você não gosta? Usando sua opção favorita acima, que tal uma função de registro como esta:

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)

Isso também tornaria trivial adicionar lógica adicional para dividir a string registrada em várias linhas se a linha for maior do que o comprimento desejado.


11

A maneira mais limpa que encontrei é inspirada no guia de estilo sql .

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

Essencialmente, as palavras-chave que começam uma cláusula devem ser alinhadas à direita e os nomes dos campos, etc., devem ser alinhados à esquerda. Isso parece muito legal e também é mais fácil de depurar.


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'

se o valor da condição for uma string, você pode fazer assim:

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
Por favor, nunca faça isso. É chamado de injeção de SQL e é muito perigoso. Praticamente toda biblioteca de banco de dados Python oferece um recurso para usar parâmetros. Se você se pega usando format()strings SQL, é um grande cheiro de código.
mattmc3

Acho que não podemos usar isso, você tem que validar os parâmetros antes de usar, e você deve saber o que passa.
pangpang

Validar é muito mais sujeito a erros do que apenas usar where condition1=:field1e depois passar os valores como parâmetros. Se você estiver usando .format(), haverá uma maneira de ';DROP TABLE Usersinserir um em seu SQL. Dê uma olhada no PEP-249 para saber como usar os parâmetros corretamente. python.org/dev/peps/pep-0249/#paramstyle
mattmc3

0

Para evitar a formatação totalmente , acho que uma ótima solução é usar procedimentos .

Chamar um procedimento fornece o resultado de qualquer consulta que você deseja colocar neste procedimento. Na verdade, você pode processar várias consultas em um procedimento. A chamada retornará apenas a última consulta que foi chamada.

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;

Pitão

sql =('call example;')

-1

você pode colocar os nomes dos campos em uma matriz "campos" e, em seguida:


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

se sua lista de condições aumentar, você pode fazer o mesmo, usando 'e' .join (condições)
jcomeau_ictx

com sua solução, a consulta seria ainda mais difícil de editar do que com Option_4, e seria difícil de ler também.
ssoler

@ssoler, isso depende de como se faz as coisas. Eu declaro algumas variáveis ​​em meus programas e uso arrays de strings, o que torna métodos como o acima muito úteis e fáceis de manter, pelo menos para mim.
jcomeau_ictx

-1

Eu sugeriria ficar com a opção 2 (estou sempre usando para consultas mais complexas do que SELECT * FROM table) e se você quiser imprimi-lo de uma maneira agradável, você pode sempre usar um módulo separado .


-1

Para consultas curtas que podem caber em uma ou duas linhas, uso a solução literal de string na solução mais votada acima. Para consultas mais longas, divido-as em .sqlarquivos. Em seguida, uso uma função de wrapper para carregar o arquivo e executar o script, algo como:

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)

É claro que isso geralmente fica dentro de uma classe, então geralmente não preciso passar cursorexplicitamente. Eu também geralmente uso codecs.open(), mas isso dá uma ideia geral. Então, os scripts SQL são completamente autocontidos em seus próprios arquivos com seu próprio realce de sintaxe.


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

[editar em responder ao comentário]
Ter uma string SQL dentro de um método NÃO significa que você precisa "tabulá-la":

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

IMO, é o mesmo que Option_2
ssoler

@ssoler: Sua Option_2 possui espaços à esquerda em todas as linhas; observe que seu exemplo omite os espaços iniciais select. Minha resposta não tem espaços iniciais. O que o leva a formar a opinião de que são iguais?
John Machin,

Se você colocar sua string sql dentro de um método, terá que tabular todas as linhas (Opção_2). Uma solução possível para isso é Option_3.
ssoler

@ssoler: Desculpe, não entendo esse comentário. Por favor, olhe minha resposta atualizada.
John Machin,

Sua resposta atualizada é minha Opção_3, não é? Não gosto dessa opção porque quebra a clareza do código bem tabulado.
ssoler
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.