web-dev-qa-db-fra.com

Exécuter "SELECT ... WHERE ... IN ..." avec MySQLdb

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 fooids 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 bars 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?

50
unutbu

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.

41
Robert Bergs

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)
66
João Silva

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.

10
Guglielmo Celata

cela fonctionne pour moi:

myTuple= Tuple(myList)
sql="select fooid from foo where bar in "+str(myTuple)
cursor.execute(sql)
5
shanal

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)
2
satru

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)
2
Smooth Customer

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',))
2
Luci Stanescu

Très simple:

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)).

1
Mizanur Rahman