Передача параметра в DB .execute для списка WHERE IN... INT

С помощью спецификации API БД Python вы можете передать аргумент параметров в метод execute(). Часть моего утверждения - это предложение WHERE IN, и я использовал кортеж для заполнения IN. Например:

params = ((3, 2, 1), )
stmt = "SELECT * FROM table WHERE id IN %s"
db.execute(stmt, params)

Но когда я сталкиваюсь с ситуацией, когда кортеж параметра является только кортежем из 1 элемента, выполнение завершается неудачно.

ProgrammingError: ОШИБКА: синтаксическая ошибка в или около ")"
ЛИНИЯ 13: ГДЕ ИД (3,)

Как я могу заставить кортеж правильно работать с предложением?

4 ответа

Решение

Изменить: Пожалуйста, как @rspeer упоминает в комментарии, принять меры предосторожности, чтобы защитить себя от атаки SQL инъекций.

Тестирование с pg8000 (интерфейс Pure-Python, совместимый с DB-API 2.0, с ядром базы данных PostgreSQL):

Это рекомендуемый способ передачи нескольких параметров в предложение "IN".

params = [3,2,1]
stmt = 'SELECT * FROM table WHERE id IN (%s)' % ','.join('%s' for i in params)
cursor.execute(stmt, params)

Еще одно редактирование (полностью протестированный и рабочий пример):

>>> from pg8000 import DBAPI
>>> conn = DBAPI.connect(user="a", database="d", host="localhost", password="p")
>>> c = conn.cursor()
>>> prms = [1,2,3]
>>> stmt = 'SELECT * FROM table WHERE id IN (%s)' % ','.join('%s' for i in prms)
>>> c.execute(stmt,prms)
>>> c.fetchall()
((1, u'myitem1'), (2, u'myitem2'), (3, u'myitem3'))

Ошибка идет через запятую после 3. Просто оставьте ее для единичных значений, и все готово.

params = ((3), ... )
stmt = "SELECT * FROM table WHERE id IN %s"
db.execute(stmt, params)

Как сказано в вопросе, следующее не удастся:

params = ((3, 2, 1), )
stmt = "SELECT * FROM table WHERE id IN %s"
db.execute(stmt, params)

После pg8000 документы вIN можно заменить на ANY() чтобы дать тот же результат:

params = ((3, 2, 1), )
stmt = "SELECT * FROM table WHERE id = ANY(%s)"
db.execute(stmt, params)

Это отправляет запрос и параметры отдельно на сервер, избегая атак с использованием SQL-инъекций.

Принятый ответ рискует SQL-инъекцией; Вы никогда не должны передавать пользовательский ввод непосредственно в базу данных. Вместо этого сгенерируйте запрос с правильным количеством заполнителей, а затем разрешите экранированию pg8000:

params = [3,2,1]
# SELECT * from table where id in (%s,%s,%s)
stmt = 'SELECT * FROM table WHERE id IN ({})'.format(','.join(['%s']*len(params)))
cursor.execute(stmt, tuple(params))

Возможно, это не ответ на вопрос, который вы задали, но я думаю, что это может решить вашу проблему.

DB-API в Python, похоже, не дает вам возможности передавать кортежи как безопасно замененные параметры. Принятый ответ от Берни использует Питон % оператор замещения, что небезопасно.

Однако вам может не потребоваться передавать кортежи в качестве параметров, особенно если нужный кортеж является результатом другого запроса SQL (как вы указали Дэниелу). Вместо этого вы можете использовать подзапросы SQL.

Если набор идентификаторов, которые вы хотите в предложении IN, является результатом SELECT id FROM other_table WHERE use=true, например:

stmt = "SELECT * FROM table WHERE id IN (SELECT id FROM other_table WHERE use=true)"
db.execute(stmt)

И это тоже можно параметризировать (безопасный способ). Если вы хотите выбрать идентификаторы с данными parent_id:

stmt = "SELECT * FROM table WHERE id IN (SELECT id FROM other_table WHERE parent_id=%s)"
params = (parent_id,)
db.execute(stmt, params)

Решение с ф-струной.

params = [...]
stmt = f"SELECT * FROM table WHERE id IN ({','.join(['%s']*len(params ),)})"
db.execute(stmt, params)

Если есть другой заполнитель параметров, он будет таким

age = 18
params = [...]
stmt = f"SELECT * FROM table WHERE age>%s AND id IN ({','.join(['%s']*len(params ),)})"
db.execute(stmt, tuple([age] + params))
Другие вопросы по тегам