liste python dans une requête SQL en tant que paramètre

125

J'ai une liste de python, dis l

l = [1,5,8]

Je veux écrire une requête SQL pour obtenir les données de tous les éléments de la liste, disons

select name from students where id = |IN THE LIST l|

Comment faire cela?

Mohit Ranka
la source

Réponses:

113

Jusqu'à présent, les réponses ont été des modèles de valeurs dans une chaîne SQL simple. C'est tout à fait correct pour les entiers, mais si nous voulions le faire pour les chaînes, nous obtenons le problème d'échappement.

Voici une variante utilisant une requête paramétrée qui fonctionnerait pour les deux:

placeholder= '?' # For SQLite. See DBAPI paramstyle.
placeholders= ', '.join(placeholder for unused in l)
query= 'SELECT name FROM students WHERE id IN (%s)' % placeholders
cursor.execute(query, l)
bobince
la source
11
','.join(placeholder * len(l))serait un peu plus court tout en restant lisible à mon humble avis
ThiefMaster
7
@Thiefmaster: oui, devrait être ([placeholder]*len(l))dans le cas général car l'espace réservé peut être composé de plusieurs caractères.
bobince
1
@bobince dans mon cas, j'ai assigné une variable a = 'john' et b = 'snow' et l'ai stockée dans un tuple avec le nom got = ['a, b'] et ai effectué la même requête. En faisant cela, j'ai eu l'erreur de type erroy ie: tous les arguments ne sont pas convertis pendant le formatage de la chaîne. Comment suis-je censé le résoudre
TechJhola
2
Pourquoi voulez-vous rejoindre "à la main" plutôt que de laisser ce travail au dbapi? la clé est d'utiliser un tuple plutôt qu'une liste ...
Alessandro Dentella
5
Sur la base de cette réponse, voici une solution à une seule ligne pour Python 3.6 cursor.execute(f'SELECT name FROM students WHERE id IN ({','.join('?' for _ in l)})', l) . @bobince, vous devriez également remarquer dans votre solution que l'utilisation ?est le moyen le plus sûr d'éviter l'injection SQL. Il y a beaucoup de réponses ici qui sont vulnérables, essentiellement celles qui concaténent des chaînes en python.
toto_tico
60

Le moyen le plus simple est de transformer la liste en tuplepremier

t = tuple(l)
query = "select name from studens where id IN {}".format(t)
Amir Imani
la source
1
C'est en fait la bonne réponse. Je ne sais pas pourquoi il a été ignoré. Commencez par configurer la liste l, puis utilisez tuple, puis passez le tuple dans la requête. bien joué.
MEdwin
11
Comme indiqué par @renstrm, cela ne fonctionne pas si l contient juste un élément, vous vous retrouverez avec id IN (1,). Ce qui est une erreur de syntaxe.
Doubledown
1
@Boris si vous ne pouvez pas garantir que votre entrée est toujours une liste, vous pouvez faire quelque chose de similaire à celui-ci stackoverflow.com/questions/38821586/... avant de passer l'argument à votre requête
Amir Imani
10
Ceci, comme la plupart des réponses ici autres que celle acceptée, est vulnérable à l'injection SQL et ne doit pas être utilisé.
Bacon Bits
2
@BaconBits Fair avertissement, mais pour certaines applications où l'injection SQL n'est pas un problème (par exemple, l'analyse d'une base de données où je suis le seul utilisateur), cela fera l'affaire.
irene le
26

Ne le compliquez pas, la solution est simple.

l = [1,5,8]

l = tuple(l)

params = {'l': l}

cursor.execute('SELECT * FROM table where id in %(l)s',params)

entrez la description de l'image ici

J'espère que cela a aidé !!!

allsyed
la source
10
Cela ne fonctionne pas s'il lne contient qu'un seul élément, vous vous retrouverez avec id IN (1,). Ce qui est une erreur de syntaxe.
renstrm
3
Si l ne contient qu'un seul élément, assurez-vous que c'est un tuple et cela fonctionnera. Cette solution est à mon avis plus claire que celle acceptée.
Alessandro Dentella
2
Mais cela ne fonctionnera toujours pas si le tuple est vide, il doit donc y avoir une vérification supplémentaire avant l'exécution de la requête.
Никита Конин
2
Cela ne fonctionne pas pour moi avec sqlite3. Avec quelle bibliothèque avez-vous testé cela?
Nick Chammas
1
Bonne solution, mais @renstrm et Никита-Конин ont raison de demander des vérifications supplémentaires lorsque le tuple a un seul élément ou aucun élément.
Anish Sana
23

Le SQL que vous voulez est

select name from studens where id in (1, 5, 8)

Si vous voulez construire cela à partir du python, vous pouvez utiliser

l = [1, 5, 8]
sql_query = 'select name from studens where id in (' + ','.join(map(str, l)) + ')'

La fonction map transformera la liste en une liste de chaînes qui peuvent être collées ensemble par des virgules à l'aide de la méthode str.join .

Alternativement:

l = [1, 5, 8]
sql_query = 'select name from studens where id in (' + ','.join((str(n) for n in l)) + ')'

si vous préférez les expressions génératrices à la fonction map.

MISE À JOUR: S. Lott mentionne dans les commentaires que les liaisons Python SQLite ne prennent pas en charge les séquences. Dans ce cas, vous voudrez peut-être

select name from studens where id = 1 or id = 5 or id = 8

Généré par

sql_query = 'select name from studens where ' + ' or '.join(('id = ' + str(n) for n in l))
Blair Conrad
la source
2
:-( La liaison Python SQLite ne prend pas en charge les séquences.
S.Lott
Oh? Je n'ai pas réalisé. Là encore, je ne savais pas que nous allions chercher une solution de liaison SQLite.
Blair Conrad
Désolé, je ne suggère ni n'implique SQLite - juste triste que les liaisons pour les listes ne fonctionnent pas là-bas.
S.Lott
11

string.join les valeurs de la liste séparées par des virgules et utilisez l' opérateur format pour former une chaîne de requête.

myquery = "select name from studens where id in (%s)" % ",".join(map(str,mylist))

(Merci, blair-conrad )

gimel
la source
2
Étant donné que cette approche n'utilise pas la substitution de paramètres de base de données, elle vous expose à des attaques par injection SQL. Le %formatage utilisé ici est simplement un formatage de chaîne Python.
Nick Chammas
8

J'aime la réponse de Bobince:

placeholder= '?' # For SQLite. See DBAPI paramstyle.
placeholders= ', '.join(placeholder for unused in l)
query= 'SELECT name FROM students WHERE id IN (%s)' % placeholders
cursor.execute(query, l)

Mais j'ai remarqué ceci:

placeholders= ', '.join(placeholder for unused in l)

Peut être remplacé par:

placeholders= ', '.join(placeholder*len(l))

Je trouve cela plus direct mais moins intelligent et moins général. Ici, il lest nécessaire d'avoir une longueur (c'est-à-dire faire référence à un objet qui définit une __len__méthode), ce qui ne devrait pas poser de problème. Mais l'espace réservé doit également être un caractère unique. Pour prendre en charge une utilisation d'espace réservé à plusieurs caractères:

placeholders= ', '.join([placeholder]*len(l))
Jimhark
la source
2

Solution pour la réponse @umounted, car cela a rompu avec un tuple à un élément, puisque (1,) n'est pas un SQL valide .:

>>> random_ids = [1234,123,54,56,57,58,78,91]
>>> cursor.execute("create table test (id)")
>>> for item in random_ids:
    cursor.execute("insert into test values (%d)" % item)
>>> sublist = [56,57,58]
>>> cursor.execute("select id from test where id in %s" % str(tuple(sublist)).replace(',)',')'))
>>> a = cursor.fetchall()
>>> a
[(56,), (57,), (58,)]

Autre solution pour la chaîne SQL:

cursor.execute("select id from test where id in (%s)" % ('"'+'", "'.join(l)+'"'))
Ximix
la source
5
Ce n'est pas une meilleure solution en raison de l'injection SQL.
Anurag le
2
placeholders= ', '.join("'{"+str(i)+"}'" for i in range(len(l)))
query="select name from students where id (%s)"%placeholders
query=query.format(*l)
cursor.execute(query)

Cela devrait résoudre votre problème.

Rishabh Jain
la source
2

Si vous utilisez PostgreSQL avec la bibliothèque Psycopg2, vous pouvez laisser son adaptation de tuple faire tout l'échappement et l'interpolation de chaîne pour vous, par exemple:

ids = [1,2,3]
cur.execute(
  "SELECT * FROM foo WHERE id IN %s",
  [tuple(ids)])

c'est-à-dire assurez-vous simplement que vous passez le INparamètre en tant que tuple. si c'est un, listvous pouvez utiliser la = ANYsyntaxe du tableau :

cur.execute(
  "SELECT * FROM foo WHERE id = ANY (%s)",
  [list(ids)])

notez que ces deux éléments seront transformés en le même plan de requête, vous devez donc simplement utiliser celui qui est le plus facile. Par exemple, si votre liste vient dans un tuple, utilisez la première, si elle est stockée dans une liste, utilisez la seconde.

Sam Mason
la source
1

Par exemple, si vous voulez la requête SQL:

select name from studens where id in (1, 5, 8)

Qu'en est-il de:

my_list = [1, 5, 8]
cur.execute("select name from studens where id in %s" % repr(my_list).replace('[','(').replace(']',')') )
pgalilea
la source
1

une solution plus simple:

lst = [1,2,3,a,b,c]

query = f"""SELECT * FROM table WHERE IN {str(lst)[1:-1}"""
Omar Omeiri
la source
1
l = [1] # or [1,2,3]

query = "SELECT * FROM table WHERE id IN :l"
params = {'l' : tuple(l)}
cursor.execute(query, params)

La :varnotation semble plus simple. (Python 3.7)

user13476428
la source
0

Cela utilise la substitution de paramètres et prend en charge le cas de liste à valeur unique:

l = [1,5,8]

get_operator = lambda x: '=' if len(x) == 1 else 'IN'
get_value = lambda x: int(x[0]) if len(x) == 1 else x

query = 'SELECT * FROM table where id ' + get_operator(l) + ' %s'

cursor.execute(query, (get_value(l),))
Roland Mechler
la source