implodindo uma lista para uso em uma cláusula python MySQLDB IN

84

Eu sei como mapear uma lista para uma string:

foostring = ",".join( map(str, list_of_ids) )

E eu sei que posso usar o seguinte para colocar essa string em uma cláusula IN:

cursor.execute("DELETE FROM foo.bar WHERE baz IN ('%s')" % (foostring))

O que eu preciso é realizar a mesma coisa com SEGURANÇA (evitando injeção de SQL) usando MySQLDB. No exemplo acima, porque foostring não é passado como um argumento a ser executado, ele é vulnerável. Eu também tenho que citar e escapar fora da biblioteca mysql.

(Há uma pergunta SO relacionada , mas as respostas listadas não funcionam para MySQLDB ou são vulneráveis ​​à injeção de SQL.)

mluebke
fonte
Você pode se inspirar em uma pergunta semelhante feita em php stackoverflow.com/questions/327274/…
Zoredache
Possível duplicata da lista python na consulta sql como parâmetro
Kamil Sindi
@mluebke Alguma ideia sobre passar várias listas na consulta?
Dipen Dedania

Respostas:

157

Use list_of_idsdiretamente:

format_strings = ','.join(['%s'] * len(list_of_ids))
cursor.execute("DELETE FROM foo.bar WHERE baz IN (%s)" % format_strings,
                tuple(list_of_ids))

Dessa forma, você evita ter que citar a si mesmo e evita todos os tipos de injeção de sql.

Observe que data ( list_of_ids) está indo diretamente para o driver do mysql, como um parâmetro (não no texto da consulta), portanto, não há injeção. Você pode deixar qualquer caractere que desejar na string, sem necessidade de remover ou citar caracteres.

nosklo
fonte
2
@heikogerlach: Não estou citando% s ... A primeira linha cria uma string de "% s,% s,% s" ... do mesmo tamanho de list_of_ids.
nosklo
Argh, você está certo. Precisa procurar mais. De alguma forma, eu confundi tudo. Boa solução, no entanto.
Isso funcionará no sqlite também? Porque acabei de experimentar e parece apontar erros de sintaxe.
Sohaib de
@Sohaib no sqlite o caractere substituto ?não é, %sentão funcionaria se você alterasse a primeira linha para format_strings = ','.join('?' * len(list_of_ids)).
nosklo
1
@kdas, no seu caso, você não deseja que a % format_stringsparte altere os outros %smarcadores de posição em sua consulta, apenas o IN (%s)marcador de posição - A maneira de fazer isso é dobrar todos os %caracteres, exceto aquele que você deseja substituir:query = ("select distinct cln from vcf_commits where branch like %%s and repository like %%s and filename in (%s) and author not like %%s" % format_strings,); cursor.execute(query, (branch, repository) + tuple(fname_list) + (invalid_author,))
nosklo
5

Embora esta questão seja bastante antiga, achei melhor deixar uma resposta caso outra pessoa estivesse procurando o que eu queria

A resposta aceita fica confusa quando temos muitos parâmetros ou se queremos usar parâmetros nomeados

Depois de algumas tentativas

ids = [5, 3, ...]  # list of ids
cursor.execute('''
SELECT 
...
WHERE
  id IN %(ids)s
  AND created_at > %(start_dt)s
''', {
  'ids': tuple(ids), 'start_dt': '2019-10-31 00:00:00'
})

Testado com python2.7,pymysql==0.7.11

markk
fonte
2
Isso não funciona com python 3 e mysql-connector-python 8.0.21. É retornado um erro "A tupla do Python não pode ser convertida para o tipo MySQL".
Rubms
-1

Se você usa Django 2.0 or 2.1e Python 3.6, este é o caminho certo:

from django.db import connection
RESULT_COLS = ['col1', 'col2', 'col3']
RESULT_COLS_STR = ', '.join(['a.'+'`'+i+'`' for i in RESULT_COLS])
QUERY_INDEX = RESULT_COLS[0]

TABLE_NAME = 'test'
search_value = ['ab', 'cd', 'ef']  # <-- a list
query = (
    f'SELECT DISTINCT {RESULT_COLS_STR} FROM {TABLE_NAME} a '
    f'WHERE a.`{RESULT_COLS[0]}` IN %s '
    f'ORDER BY a.`{RESULT_COLS[0]}`;'
)  # <- 'SELECT DISTINCT a.`col1`, a.`col2`, a.`col3` FROM test a WHERE a.`col1` IN %s ORDER BY a.`col1`;'
with connection.cursor() as cursor:
    cursor.execute(query, params=[search_value])  # params is a list with a list as its element

ref: https://stackoverflow.com/a/23891759/2803344 https://docs.djangoproject.com/en/2.1/topics/db/sql/#passing-parameters-into-raw

Belter
fonte
-1

Embora esta questão seja bastante antiga. Estou compartilhando minha solução, se puder ajudar alguém.

list_to_check = ['A', 'B'] cursor.execute("DELETE FROM foo.bar WHERE baz IN ({})".format(str(list_to_check)[1:-1])

Testado com Python=3.6

Aditya Sahu
fonte
Temo que esta solução seja vulnerável a ataques de injeção de SQL, pois o fornecido list_to_checknão está sendo escapado de SQL. É por isso que passar os valores como parâmetros para executeé mais apropriado. Use esta solução com muito cuidado (ou seja, os IDs de entrada não são recebidos como parâmetros de fora de seu aplicativo), pois alguém poderia usar isso para atacar seu sistema e acessar seu banco de dados.
Rubms
-2

Outra solução simples usando compreensão de lista:

# creating a new list of strings and convert to tuple
sql_list = tuple([ key.encode("UTF-8") for key in list_of_ids ])

# replace "{}" with "('id1','id2',...'idlast')"
cursor.execute("DELETE FROM foo.bar WHERE baz IN {}".format(sql_list))
Chenchuk
fonte
-4
list_of_ids = [ 1, 2, 3]
query = "select * from table where x in %s" % str(tuple(list_of_ids))
print query

Isso pode funcionar para alguns casos de uso, se você não quiser se preocupar com o método no qual deve passar argumentos para completar a string de consulta e gostaria de invocar apenas cursror.execute(query).

Outra forma poderia ser:

"select * from table where x in (%s)" % ', '.join(str(id) for id in list_of_ids)
Anurag Nilesh
fonte
-7

Muito simples: basta usar a formação abaixo

regras_id = ["9", "10"]

sql1 = "SELECT * FROM assis_rules_staff WHERE id em (" + "," .join (map (str, rules_id)) + ")"

"," .join (map (str, rules_id))

Mizanur Rahman
fonte
Onde ele faz a cotação de sql e isso não está usando um literal em vez de variáveis ​​de ligação?
eckes de
Não precisa, ele simplesmente está funcionando bem. Você pode testar porque a formação da tupla é convertida diretamente como string com as primeiras chaves ("9", "10"). Que ajustam a formação do sql. Então você não precisa de outra formação para fazer é sql adjastable
Mizanur Rahman
1
e se um rules_idcontém "); DROP TABLES Bobby --?
eckes de
Já disse "implodindo uma lista" não ") ... então antes da consulta você precisa validar
Mizanur Rahman
ou use: sql1 = "SELECIONE * FROM frequência_rules_staff WHERE id em (" + "," .join (map (str, rules_id)) + ")"
Mizanur Rahman