Je ne parviens pas à exécuter du code SQL à partir de Python, même si ce type de code fonctionne correctement à partir de la ligne de commande mysql
.
Le tableau ressemble à ceci:
mysql> SELECT * FROM foo;
+-------+-----+
| fooid | bar |
+-------+-----+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
+-------+-----+
4 rows in set (0.00 sec)
Je peux exécuter la requête SQL suivante à partir de la ligne de commande mysql, sans problème:
mysql> SELECT fooid FROM foo WHERE bar IN ('A','C');
SELECT fooid FROM foo WHERE bar IN ('A','C');
+-------+
| fooid |
+-------+
| 1 |
| 3 |
+-------+
2 rows in set (0.00 sec)
Cependant, lorsque j'essaie de faire la même chose depuis Python, je ne reçois aucune ligne, alors que je m'attendais à 2 lignes:
import MySQLdb
import config
connection=MySQLdb.connect(
Host=config.Host,user=config.USER,passwd=config.PASS,db='test')
cursor=connection.cursor()
sql='SELECT fooid FROM foo WHERE bar IN %s'
args=[['A','C']]
cursor.execute(sql,args)
data=cursor.fetchall()
print(data)
# ()
La question est donc la suivante: comment modifier le code python pour sélectionner les fooid
s où bar
est dans ('A','C')
?
En passant, j'ai remarqué que si je permutais les rôles bar
et fooid
, je pouvais obtenir le code pour sélectionner les bar
s où fooid
est correctement dans (1,3)
. Je ne comprends pas pourquoi une de ces requêtes (ci-dessous) fonctionne, alors que l'autre (ci-dessus) ne fonctionne pas.
sql='SELECT bar FROM foo WHERE fooid IN %s'
args=[[1,3]]
cursor.execute(sql,args)
data=cursor.fetchall()
print(data)
# (('A',), ('C',))
Et juste pour être absolument clair, voici comment la table foo
a été créée:
mysql> DROP TABLE IF EXISTS foo;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE `foo` (
`fooid` int(11) NOT NULL AUTO_INCREMENT,
`bar` varchar(10) NOT NULL,
PRIMARY KEY (`fooid`));
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT into foo (bar) values ('A'),('B'),('C'),('D');
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
Edit : Lorsque j'active le journal de requête général avec mysqld -l /tmp/myquery.log
, Je vois
mysqld, Version: 5.1.37-1ubuntu5.5-log ((Ubuntu)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
110101 11:45:41 1 Connect unutbu@localhost on test
1 Query set autocommit=0
1 Query SELECT fooid FROM foo WHERE bar IN ("'A'", "'C'")
1 Query SELECT bar FROM foo WHERE fooid IN ('1', '3')
1 Quit
En effet, il semble que trop de guillemets soient placés autour de A
et C
.
Grâce au commentaire de @ Amber, je comprends mieux ce qui ne va pas. MySQLdb convertit l'argument paramétré ['A','C']
en ("'A'","'C'")
.
Existe-t-il un moyen de créer une requête paramétrée à l’aide de la syntaxe SQL IN
? Ou faut-il construire manuellement la chaîne SQL?
Voici une solution similaire qui, à mon avis, est plus efficace pour construire la liste des chaînes% s dans le code SQL:
Utilisez le
list_of_ids
directement:format_strings = ','.join(['%s'] * len(list_of_ids)) cursor.execute("DELETE FROM foo.bar WHERE baz IN (%s)" % format_strings, Tuple(list_of_ids))
De cette façon, vous évitez de vous citer vous-même et évitez toutes sortes d’injections SQL.
Notez que les données (
list_of_ids
) vont directement au pilote de mysql, en tant que paramètre (pas dans le texte de la requête), il n'y a donc pas d'injection. Vous pouvez laisser les caractères de votre choix dans la chaîne, vous n'avez pas besoin de supprimer ou de citer des caractères.
Malheureusement, vous devez construire manuellement les paramètres de requête car, autant que je sache, il n'existe pas de méthode intégrée bind
pour lier une variable list
à une clause IN
, semblable à la fonction setParameterList()
d'Hibernate. Cependant, vous pouvez accomplir la même chose avec ce qui suit:
Python 3:
args=['A', 'C']
sql='SELECT fooid FROM foo WHERE bar IN (%s)'
in_p=', '.join(list(map(lambda x: '%s', args)))
sql = sql % in_p
cursor.execute(sql, args)
Python 2:
args=['A', 'C']
sql='SELECT fooid FROM foo WHERE bar IN (%s)'
in_p=', '.join(map(lambda x: '%s', args))
sql = sql % in_p
cursor.execute(sql, args)
Si vous avez d'autres paramètres dans la requête, au-delà de la liste IN, l'extension suivante de la réponse de JG peut être utile.
ids = [1, 5, 7, 213]
sql = "select * from person where type=%s and id in (%s)"
in_ids = ', '.join(map(lambda x: '%s', ids))
sql = sql % ('%s', in_ids)
params = []
params.append(type)
params.extend(ids)
cursor.execute(sql, Tuple(params))
En d'autres termes, joignez tous les paramètres d'un tableau linéaire, puis transmettez-le sous forme de tuple à la méthode execute.
cela fonctionne pour moi:
myTuple= Tuple(myList)
sql="select fooid from foo where bar in "+str(myTuple)
cursor.execute(sql)
Peut-être pourrions-nous créer une fonction pour faire ce que João a proposé? Quelque chose comme:
def cursor_exec(cursor, query, params):
expansion_params= []
real_params = []
for p in params:
if isinstance(p, (Tuple, list)):
real_params.extend(p)
expansion_params.append( ("%s,"*len(p))[:-1] )
else:
real_params.append(p)
expansion_params.append("%s")
real_query = query % expansion_params
cursor.execute(real_query, real_params)
J'ai essayé toutes les variantes de la solution de João pour faire en sorte qu'une requête de la liste IN fonctionne avec le wrapper mysql de Tornado. Il s'avère que l'ajout de "*" à la liste var "* args" a fait l'affaire.
args=['A', 'C']
sql='SELECT fooid FROM foo WHERE bar IN (%s)'
in_p=', '.join(list(map(lambda x: '%s', args)))
sql = sql % in_p
db.query(sql, *args)
Pour améliorer le code de João et de satru, je suggère de créer un mixin de curseur pouvant être utilisé pour construire un curseur avec une exécution qui accepte les itérables imbriqués et les gère correctement. Un meilleur nom serait Nice, cependant ... Pour Python3, utilisez str
au lieu de basestring
.
from MySQLdb.cursors import Cursor
class BetterExecuteMixin(object):
"""
This mixin class provides an implementation of the execute method
that properly handles sequence arguments for use with IN tests.
Examples:
execute('SELECT * FROM foo WHERE id IN (%s) AND type=%s', ([1,2,3], 'bar'))
# Notice that when the sequence is the only argument, you still need
# a surrounding Tuple:
execute('SELECT * FROM foo WHERE id IN (%s)', ([1,2,3],))
"""
def execute(self, query, args=None):
if args is not None:
try:
iter(args)
except TypeError:
args = (args,)
else:
if isinstance(args, basestring):
args = (args,)
real_params = []
placeholders = []
for arg in args:
# sequences that we treat as a single argument
if isinstance(arg, basestring):
real_params.append(arg)
placeholders.append('%s')
continue
try:
real_params.extend(arg)
placeholders.append(','.join(['%s']*len(arg)))
except TypeError:
real_params.append(arg)
placeholders.append('%s')
args = real_params
query = query % Tuple(placeholders)
return super(BetterExecuteMixin, self).execute(query, args)
class BetterCursor(BetterExecuteMixin, Cursor):
pass
Ceci peut alors être utilisé comme suit (et il est toujours compatible avec les versions antérieures!):
import MySQLdb
conn = MySQLdb.connect(user='user', passwd='pass', db='dbname', Host='Host',
cursorclass=BetterCursor)
cursor = conn.cursor()
cursor.execute('SELECT * FROM foo WHERE id IN (%s) AND type=%s', ([1,2,3], 'bar'))
cursor.execute('SELECT * FROM foo WHERE id IN (%s)', ([1,2,3],))
cursor.execute('SELECT * FROM foo WHERE type IN (%s)', (['bar', 'moo'],))
cursor.execute('SELECT * FROM foo WHERE type=%s', 'bar')
cursor.execute('SELECT * FROM foo WHERE type=%s', ('bar',))
Utilisez simplement la formation ci-dessous ###
rules_id = ["9","10"]
sql2 = "SELECT * FROM attendance_rules_staff WHERE id in"+str(Tuple(rules_id))
notez la str(Tuple(rules_id))
.