Formatage de la chaîne de requête SQL Python

93

J'essaie de trouver le meilleur moyen de formater une chaîne de requête SQL. Lorsque je débogue mon application, j'aimerais me connecter pour classer toutes les chaînes de requête SQL, et il est important que la chaîne soit correctement formatée.

Option 1

def myquery():
    sql = "select field1, field2, field3, field4 from table where condition1=1 and condition2=2"
    con = mymodule.get_connection()
    ...
  • C'est bon pour imprimer la chaîne sql.
  • Ce n'est pas une bonne solution si la chaîne est longue et ne correspond pas à la largeur standard de 80 caractères.

Option 2

def query():
    sql = """
        select field1, field2, field3, field4
        from table
        where condition1=1
        and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Ici, le code est clair, mais lorsque vous imprimez la chaîne de requête SQL, vous obtenez tous ces espaces blancs ennuyeux.

    u '\ nsélectionnez champ1, champ2, champ3, champ4 \ n_ _ ___ de la table \ n _ ___ où condition1 = 1 \ n _ ___ _et condition2 = 2'

Remarque: j'ai remplacé les espaces blancs par des traits de soulignement _, car ils sont coupés par l'éditeur

Option 3

def query():
    sql = """select field1, field2, field3, field4
from table
where condition1=1
and condition2=2"""
    con = mymodule.get_connection()
    ...
  • Je n'aime pas cette option car elle brise la clarté du code bien tabulé.

Option 4

def query():
    sql = "select field1, field2, field3, field4 " \
          "from table " \
          "where condition1=1 " \
          "and condition2=2 "
    con = mymodule.get_connection()    
    ...
  • Je n'aime pas cette option car toute la saisie supplémentaire dans chaque ligne et il est également difficile de modifier la requête.

Pour moi, la meilleure solution serait l' option 2 mais je n'aime pas les espaces supplémentaires lorsque j'imprime la chaîne sql.

Connaissez-vous d'autres options?

ssoler
la source
C'est ce que les gens de Psycopg appellent une approche naïve de la composition des chaînes de requête, par exemple en utilisant la concaténation de chaînes - initd.org/psycopg/docs/… . Utilisez plutôt des paramètres de requête pour éviter les attaques par injection SQL et pour convertir automatiquement des objets Python vers et à partir de littéraux SQL. stackoverflow.com/questions/3134691/…
Matthew Cornell
Cette question n'est en fait pas spécifique aux requêtes SQL, mais s'applique généralement au formatage de chaînes multilignes en Python. La balise SQL doit être supprimée.
cstork

Réponses:

130

Désolé d'avoir posté sur un fil aussi ancien - mais en tant que personne partageant également une passion pour le «meilleur» pythonique, j'ai pensé partager notre solution.

La solution consiste à créer des instructions SQL en utilisant la concaténation littérale de chaîne de python ( http://docs.python.org/ ), qui pourrait être qualifiée quelque part entre les options 2 et 4.

Exemple de code:

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

Fonctionne également avec les chaînes F :

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

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

Avantages:

  1. Il conserve le format pythonique «bien tabulé», mais n'ajoute pas de caractères d'espace superflus (ce qui pollue la journalisation).
  2. Cela évite la laideur de la continuation de la barre oblique inverse de l'option 4, ce qui rend difficile l'ajout d'instructions (sans parler de la cécité des espaces blancs).
  3. De plus, il est vraiment simple de développer l'instruction dans VIM (placez simplement le curseur sur le point d'insertion et appuyez sur SHIFT-O pour ouvrir une nouvelle ligne).
user590028
la source
1
Si c'est pour l'impression, je pense qu'une meilleure alternative est de l'écrire sous forme de chaîne mutiline avec """et de l'utiliser textwrap.dedent()avant la sortie
slezica
J'ai joué avec cette option, mais elle a également rendu la sortie du journal multiligne. Lors du suivi d'une application de conversation db, cela provoquait une sortie volumineuse.
user590028
1
C'est un vieux fil de discussion, mais j'utilise ce format comme meilleure pratique, mais cela devient fastidieux avec des requêtes plus longues
Jabda
7
Ne devrions-nous pas toujours utiliser des guillemets doubles "sql query"pour éviter de jouer avec les chaînes SQL (qui utilisent des guillemets simples en standard)?
tpvasconcelos
19

Vous avez évidemment envisagé de nombreuses façons d'écrire le SQL de manière à ce qu'il s'imprime correctement, mais que diriez-vous de changer l'instruction 'print' que vous utilisez pour la journalisation du débogage, plutôt que d'écrire votre SQL d'une manière que vous n'aimez pas? En utilisant votre option préférée ci-dessus, que diriez-vous d'une fonction de journalisation comme celle-ci:

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)

Cela rendrait également trivial d'ajouter une logique supplémentaire pour diviser la chaîne journalisée sur plusieurs lignes si la ligne est plus longue que la longueur souhaitée.

cdlk
la source
11

La manière la plus propre que j'ai rencontrée est inspirée du guide de style SQL .

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

Essentiellement, les mots-clés qui commencent une clause doivent être alignés à droite et les noms de champs, etc., doivent être alignés à gauche. Cela semble très soigné et est également plus facile à déboguer.

aandis
la source
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 la valeur de condition doit être une chaîne, vous pouvez faire comme ceci:

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
la source
5
Veuillez ne jamais faire ça. Cela s'appelle l'injection SQL et c'est vraiment dangereux. Presque toutes les bibliothèques de bases de données Python fournissent une fonction pour utiliser des paramètres. Si vous vous surprenez à utiliser format()des chaînes SQL, c'est une odeur de code majeure.
mattmc3
Je ne pense pas qu'on ne puisse pas l'utiliser, il faut valider les paramètres avant de l'utiliser, et il faut savoir ce que l'on passe.
pangpang
La validation est beaucoup plus sujette aux erreurs que la simple utilisation where condition1=:field1puis la transmission des valeurs en tant que paramètres. Si vous utilisez .format(), il y aura un moyen d'insérer un ';DROP TABLE Usersdans votre SQL. Jetez un œil à PEP-249 pour savoir comment utiliser correctement les paramètres. python.org/dev/peps/pep-0249/#paramstyle
mattmc3
0

Pour éviter complètement le formatage , je pense qu'une excellente solution est d'utiliser des procédures .

L'appel d'une procédure vous donne le résultat de la requête que vous souhaitez mettre dans cette procédure. Vous pouvez en fait traiter plusieurs requêtes dans une procédure. L'appel renverra simplement la dernière requête qui a été appelée.

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;

Python

sql =('call example;')
Paroofkey
la source
-1

vous pouvez mettre les noms de champs dans un tableau "fields", puis:


sql = 'select %s from table where condition1=1 and condition2=2' % (
 ', '.join(fields))
jcomeau_ictx
la source
si votre liste de conditions s'allonge, vous pouvez faire de même, en utilisant 'et' .join (conditions)
jcomeau_ictx
avec votre solution, la requête serait encore plus difficile à modifier qu'avec Option_4, et elle serait également difficile à lire.
ssoler
@ssoler, cela dépend de la façon dont on fait les choses. Je déclare quelques variables dans mes programmes, et j'utilise des tableaux de chaînes à la place, ce qui rend des méthodes comme celles ci-dessus très utiles et maintenables, du moins par moi.
jcomeau_ictx
-1

Je suggérerais de m'en tenir à l'option 2 (je l'utilise toujours pour les requêtes plus complexes que SELECT * FROM table) et si vous souhaitez l'imprimer de manière agréable, vous pouvez toujours utiliser un module séparé .

Michal Chruszcz
la source
-1

Pour les requêtes courtes qui peuvent tenir sur une ou deux lignes, j'utilise la solution littérale de chaîne dans la solution la plus votée ci-dessus. Pour les requêtes plus longues, je les divise en .sqlfichiers. J'utilise ensuite une fonction wrapper pour charger le fichier et exécuter le script, quelque chose comme:

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)

Bien sûr, cela vit souvent dans une classe, donc je n'ai généralement pas à passer cursorexplicitement. J'utilise aussi généralement codecs.open(), mais cela fait passer l'idée générale. Ensuite, les scripts SQL sont complètement autonomes dans leurs propres fichiers avec leur propre coloration syntaxique.

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

[edit in responese to comment]
Avoir une chaîne SQL dans une méthode ne signifie PAS que vous devez la «tabuler»:

>>> class Foo:
...     def fubar(self):
...         sql = """\
... select *
... from frobozz
... where zorkmids > 10
... ;"""
...         print sql
...
>>> Foo().fubar()
select *
from frobozz
where zorkmids > 10
;
>>>
John Machin
la source
OMI c'est la même chose que Option_2
ssoler
@ssoler: Votre Option_2 a des espaces de début sur toutes les lignes; notez que votre exemple omet les espaces de début avant select. Ma réponse n'a pas d'espaces de premier plan. Qu'est-ce qui vous a amené à penser qu'ils sont identiques?
John Machin
Si vous mettez votre chaîne SQL dans une méthode, vous devrez tabuler toutes les lignes (Option_2). Une solution possible à cela est Option_3.
ssoler
@ssoler: Désolé, je ne comprends pas cette remarque. Veuillez regarder ma réponse mise à jour.
John Machin
Votre réponse mise à jour est mon Option_3, n'est-ce pas? Je n'aime pas cette option car elle brise la clarté du code bien tabulé.
ssoler