Formato de cadena de consulta SQL de Python

93

Estoy tratando de encontrar la mejor manera de formatear una cadena de consulta SQL. Cuando estoy depurando mi aplicación, me gustaría iniciar sesión para archivar todas las cadenas de consulta SQL, y es importante que la cadena esté formateada correctamente.

Opción 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • Esto es bueno para imprimir la cadena sql.
  • No es una buena solución si la cadena es larga y no se ajusta al ancho estándar de 80 caracteres.

opcion 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Aquí el código es claro, pero cuando imprime la cadena de consulta SQL, obtiene todos estos molestos espacios en blanco.

    u '\ nseleccionar campo1, campo2, campo3, campo4 \ ​​n_ _ ___ de la tabla \ n _ ___ donde condición1 = 1 \ n _ ___ _ y condición2 = 2'

Nota: he reemplazado los espacios en blanco con un guión bajo _, porque el editor los recorta

Opción 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • No me gusta esta opción porque rompe la claridad del código bien tabulado.

Opción 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • No me gusta esta opción porque toda la escritura adicional en cada línea y también es difícil editar la consulta.

Para mí la mejor solución sería la Opción 2 pero no me gustan los espacios en blanco adicionales cuando imprimo la cadena sql.

¿Conoces otras opciones?

ssoler
fuente
Esto es lo que la gente de Psycopg llama un enfoque ingenuo para la composición de cadenas de consulta, por ejemplo, usando la concatenación de cadenas - initd.org/psycopg/docs/… . En su lugar, utilice parámetros de consulta para evitar ataques de inyección SQL y convertir automáticamente objetos Python hacia y desde literales SQL. stackoverflow.com/questions/3134691/…
Matthew Cornell
Esta pregunta en realidad no es específica de las consultas SQL, pero se aplica generalmente al formateo de cadenas de varias líneas en Python. Debe eliminarse la etiqueta SQL.
corcho

Respuestas:

130

Perdón por publicar en un hilo tan antiguo, pero como alguien que también comparte la pasión por lo 'mejor' pitónico, pensé en compartir nuestra solución.

La solución es construir declaraciones SQL usando la Concatenación de Literal de Cadena de Python ( http://docs.python.org/ ), que podría calificarse en algún lugar entre la Opción 2 y la Opción 4

Ejemplo de código:

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

Funciona también con 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};")

Pros:

  1. Conserva el formato pitónico 'bien tabulado', pero no agrega caracteres de espacio extraños (que contamina el registro).
  2. Evita la fealdad de la continuación de la barra invertida de la Opción 4, lo que dificulta la adición de declaraciones (sin mencionar la ceguera de los espacios en blanco).
  3. Y además, es realmente simple expandir la declaración en VIM (simplemente coloque el cursor en el punto de inserción y presione SHIFT-O para abrir una nueva línea).
user590028
fuente
1
Si esto es para imprimir, creo que una mejor alternativa es escribirlo como una cadena mutilina con """y usar textwrap.dedent()antes de la salida
slezica
Jugué con esa opción, pero también hizo que la salida del registro fuera de varias líneas. Al rastrear una aplicación db chatty, esto provocó una salida voluminosa.
user590028
1
Este es un hilo antiguo, pero he estado usando este formato como una mejor práctica, sin embargo, se vuelve tedioso con consultas más largas
Jabda
7
¿No deberíamos usar siempre comillas dobles "sql query"para evitar jugar con cadenas SQL (que usan comillas simples como estándar)?
tpvasconcelos
19

Obviamente, ha considerado muchas formas de escribir el SQL de manera que se imprima bien, pero ¿qué tal cambiar la declaración 'imprimir' que usa para el registro de depuración, en lugar de escribir su SQL de formas que no le gustan? Usando su opción favorita anterior, ¿qué tal una función 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)

Esto también haría que sea trivial agregar lógica adicional para dividir la cadena registrada en varias líneas si la línea es más larga que la longitud deseada.

cdlk
fuente
11

La forma más limpia con la que me he encontrado está inspirada en la guía de estilo sql .

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

Esencialmente, las palabras clave que comienzan una cláusula deben estar alineadas a la derecha y los nombres de los campos, etc., deben estar alineados a la izquierda. Esto se ve muy ordenado y también es más fácil de depurar.

aandis
fuente
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'

si el valor de la condición debe ser una cadena, puede hacer lo siguiente:

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'"
pangpang
fuente
5
Por favor, nunca hagas esto. Se llama inyección SQL y es realmente peligroso. Prácticamente todas las bibliotecas de bases de datos de Python ofrecen la posibilidad de utilizar parámetros. Si te das cuenta de que estás usando format()cadenas SQL, es un olor a código importante.
mattmc3
No creo que no podamos usarlo, tienes que validar los parámetros antes de usarlo, y debes saber lo que pasas.
pangpang
Validar es mucho más propenso a errores que simplemente usar where condition1=:field1y luego pasar los valores como parámetros. Si está utilizando .format(), habrá una forma de ';DROP TABLE Usersintroducir un en su SQL. Eche un vistazo a PEP-249 para saber cómo utilizar los parámetros correctamente. python.org/dev/peps/pep-0249/#paramstyle
mattmc3
0

Para evitar el formateo por completo , creo que una gran solución es utilizar procedimientos .

Llamar a un procedimiento le da el resultado de cualquier consulta que desee poner en este procedimiento. De hecho, puede procesar varias consultas dentro de un procedimiento. La llamada simplemente devolverá la última consulta que se llamó.

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ón

sql =('call example;')
Paroofkey
fuente
-1

podría poner los nombres de los campos en una matriz "campos", y luego:


sql = 'select %s from table where condition1=1 and condition2=2' % (
 ', '.join(fields))
jcomeau_ictx
fuente
si su lista de condiciones crece, puede hacer lo mismo, usando 'y' .join (condiciones)
jcomeau_ictx
con su solución, la consulta sería aún más difícil de editar que con Option_4, y también sería difícil de leer.
ssoler
@ssoler, eso depende de cómo se hagan las cosas. Declaro pocas variables en mis programas y uso matrices de cadenas en su lugar, lo que hace que métodos como el anterior sean muy útiles y fáciles de mantener, al menos para mí.
jcomeau_ictx
-1

Sugeriría ceñirse a la opción 2 (siempre la uso para consultas más complejas que SELECT * FROM table) y si desea imprimirla de una manera agradable, siempre puede usar un módulo separado .

Michal Chruszcz
fuente
-1

Para consultas cortas que pueden caber en una o dos líneas, uso la solución literal de cadena en la solución más votada anterior. Para consultas más largas, las divido en .sqlarchivos. Luego uso una función contenedora para cargar el archivo y ejecutar el 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)

Por supuesto, esto a menudo vive dentro de una clase, por lo que generalmente no tengo que aprobar cursorexplícitamente. También uso generalmente codecs.open(), pero esto hace que la idea general se entienda. Entonces, los scripts SQL son completamente autónomos en sus propios archivos con su propia sintaxis resaltada.

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

[editar en responder al comentario]
Tener una cadena SQL dentro de un método NO significa que tenga que "tabularlo":

>>> class Foo:
...     def fubar(self):
...         sql = """\
... select *
... from frobozz
... where zorkmids > 10
... ;"""
...         print sql
...
>>> Foo().fubar()
select *
from frobozz
where zorkmids > 10
;
>>>
John Machin
fuente
En mi opinión, esto es lo mismo que Option_2
ssoler
@ssoler: su Option_2 tiene espacios iniciales en todas las líneas; tenga en cuenta que su ejemplo omite los espacios iniciales antes select. Mi respuesta no tiene espacios iniciales. ¿Qué le llevó a formarse la opinión de que son iguales?
John Machin
Si pones tu cadena sql dentro de un método, tendrás que tabular todas las líneas (Option_2). Una posible solución a esto es Option_3.
ssoler
@ssoler: Lo siento, no entiendo ese comentario. Por favor, mire mi respuesta actualizada.
John Machin
Tu respuesta actualizada es mi Option_3, ¿no? No me gusta esta opción porque rompe la claridad del código bien tabulado.
ssoler