Экранирование значения SQL LIKE для Postgres с помощью psycopg2

Есть ли в psycopg2 функция экранирования значения операнда LIKE для Postgres?

Например, я могу захотеть сопоставить строки, начинающиеся со строки «20% от всего», поэтому я хочу написать что-то вроде этого:

sql = '... WHERE ... LIKE %(myvalue)s'
cursor.fetchall(sql, { 'myvalue': escape_sql_like('20% of all') + '%' }

Существует ли существующая функция escape_sql_like, которую я мог бы подключить сюда?

(Похожий вопрос на Как явно указать строковое значение в кавычках (Python DB API/Psycopg2), но я не смог найти там ответа.)


person EMP    schedule 21.01.2010    source источник


Ответы (11)


Да, это настоящий бардак. И MySQL, и PostgreSQL по умолчанию используют для этого обратную косую черту. Это ужасная боль, если вы также снова экранируете строку с помощью обратной косой черты вместо использования параметризации, и это также неверно в соответствии с ANSI SQL: 1992, в котором говорится, что по умолчанию нет дополнительных escape-символов поверх обычного экранирования строки, и следовательно, нет возможности включить литерал % или _.

Я предполагаю, что простой метод замены обратной косой черты также пойдет не так, если вы отключите экранирование обратной косой черты (которые сами по себе несовместимы с ANSI SQL), используя NO_BACKSLASH_ESCAPE sql_mode в MySQL или standard_conforming_strings conf в PostgreSQL (которым разработчики PostgreSQL угрожали сделать для пары версий сейчас).

Единственным реальным решением является использование малоизвестного синтаксиса LIKE...ESCAPE для явного указания управляющего символа для шаблона LIKE. Это используется вместо обратной косой черты в MySQL и PostgreSQL, что делает их соответствующими тому, что делают все остальные, и дает гарантированный способ включения внеполосных символов. Например, со знаком = в качестве побега:

# look for term anywhere within title
term= term.replace('=', '==').replace('%', '=%').replace('_', '=_')
sql= "SELECT * FROM things WHERE description LIKE %(like)s ESCAPE '='"
cursor.execute(sql, dict(like= '%'+term+'%'))

Это работает с базами данных, совместимыми с PostgreSQL, MySQL и ANSI SQL (конечно, по модулю paramstyle, который меняется в разных модулях db).

Все еще может быть проблема с MS SQL Server/Sybase, который, по-видимому, также допускает группы символов в стиле [a-z] в выражениях LIKE. В этом случае вы также захотите экранировать буквальный символ [ с помощью .replace('[', '=['). Однако, согласно ANSI SQL, экранирование символа, который не требует экранирования, недопустимо! (Аргх!) Таким образом, хотя он, вероятно, по-прежнему будет работать в реальных СУБД, вы все равно не будете соответствовать стандарту ANSI. вздох...

person bobince    schedule 21.01.2010
comment
standard_conforming_strings в postgres не ломает обратную косую черту в подобных запросах. По крайней мере, не в 12.7. - person 0x89; 02.07.2021

Мне удалось избежать %, используя %% в операнде LIKE.

sql_query = "select * from mytable where website like '%%.com'"
cursor.fetchall(sql_query)
person pandasuser    schedule 27.08.2019
comment
Это должно быть имхо решением. Что-то не так с этим? - person Dalbenn; 31.03.2020
comment
Может быть, это зависит от версии PostgreSQL? Этот ответ был получен через несколько лет после того, как был опубликован вопрос и текущий топ-ответ. - person wfgeo; 08.07.2020
comment
это вообще не работает. - person Jasen; 18.05.2021

Можно также посмотреть на эту проблему под другим углом. Что ты хочешь? Вам нужен запрос, который для любого строкового аргумента выполняет LIKE, добавляя '%' к аргументу. Хороший способ выразить это, не прибегая к функциям и расширениям psycopg2, мог бы быть:

sql = "... WHERE ... LIKE %(myvalue)s||'%'"
cursor.execute(sql, { 'myvalue': '20% of all'})
person fog    schedule 13.02.2013
comment
это будет соответствовать таким строкам, как 2001 had worst of all terrorism - person Jasen; 14.07.2015
comment
Нет, не будет, потому что % в аргументе будет заключено в кавычки. - person fog; 18.05.2021
comment
ничто не говорит pscopg2, что % нуждается в особой обработке. - person Jasen; 18.05.2021
comment
Это связанная переменная: по умолчанию она заключена в кавычки. - person fog; 19.05.2021
comment
да, поэтому не получится. вы должны проверить это. - person Jasen; 20.05.2021

Интересно, действительно ли нужно все вышеперечисленное? Я использую psycopg2 и просто смог использовать:

data_dict['like'] = psycopg2.Binary('%'+ match_string +'%')
cursor.execute("SELECT * FROM some_table WHERE description ILIKE %(like)s;", data_dict)
person Neha Chachra    schedule 02.06.2010
comment
Можно еще проще: `cursor.execute(SELECT * FROM some_table WHERE description LIKE %s;, ['foobar%']); - person jb.; 30.05.2012

Вместо экранирования символа процента вы можете использовать реализацию регулярных выражений PostgreSQL.

Например, следующий запрос к системным каталогам предоставит список активных запросов, которые не относятся к подсистеме автоочистки:

SELECT procpid, current_query FROM pg_stat_activity
WHERE (CURRENT_TIMESTAMP - query_start) >= '%s minute'::interval
AND current_query !~ '^autovacuum' ORDER BY (CURRENT_TIMESTAMP - query_start) DESC;

Поскольку в этом синтаксисе запроса не используется ключевое слово LIKE, вы можете делать то, что хотите... и не мутить воду в отношении python и psycopg2.

person Brian    schedule 06.02.2014

Если вы используете подготовленный оператор, то ввод будет заключен в '', чтобы предотвратить внедрение sql. Это здорово, но также предотвращает конкатенацию input + sql.

Лучшим и безопасным способом обойти это было бы передать %(s) как часть ввода.

cursor.execute('SELECT * FROM goats WHERE name LIKE %(name)s', { 'name': '%{}%'.format(name)})
person sdc    schedule 20.11.2019

Пока не удалось найти встроенную функцию, я написал довольно простую:

def escape_sql_like(s):
    return s.replace('\\', '\\\\').replace('%', '\\%').replace('_', '\\_')
person EMP    schedule 21.01.2010
comment
@JensTimmerman эта функция экранирует только аналогичные токены, чтобы использовать обычную строку, экранирующую результат, прежде чем использовать его в запросе. правильное экранирование строк зависит от сеанса standard_conforming_stings, поэтому лучше всего использовать код библиотеки. - person Jasen; 14.07.2015
comment
Короче говоря, re.sub(r'([%\\"\'_])', r'\\\1', s) - person jstaab; 09.03.2020

Я нашел лучший хак. Просто добавьте «%» к вашему поисковому запросу query_text.

con, queryset_list = psycopg2.connect(**self.config), None
cur = con.cursor(cursor_factory=RealDictCursor)
query = "SELECT * "
query += " FROM questions WHERE  body LIKE %s OR title LIKE %s  "
query += " ORDER BY questions.created_at"
cur.execute(query, ('%'+self.q+'%', '%'+self.q+'%'))
person p8ul    schedule 27.08.2018

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

person piro    schedule 16.02.2010
comment
Интересная идея, о которой я не подумал, но вам всегда нужно было бы комбинировать экранированную строку с реальными операторами LIKE (% или _), иначе вы могли бы также использовать = вместо LIKE. Если вы сделаете это, то я не уверен, в чем преимущество этого подхода по сравнению с более простым подходом, заключающимся в простом вызове функции escape. - person EMP; 17.02.2010

Я внес некоторые изменения в приведенный выше код, чтобы сделать следующее:

def escape_sql_like(SQL):
    return SQL.replace("'%", 'PERCENTLEFT').replace("%'", 'PERCENTRIGHT')

def reescape_sql_like(SQL):
    return SQL.replace('PERCENTLEFT', "'%").replace('PERCENTRIGHT', "%'")

SQL = "SELECT blah LIKE '%OUCH%' FROM blah_tbl ... "
SQL = escape_sql_like(SQL)
tmpData = (LastDate,)
SQL = cur.mogrify(SQL, tmpData)
SQL = reescape_sql_like(SQL)
cur.execute(SQL)
person Bob Turner    schedule 04.08.2011

Я думаю, что было бы проще и читабельнее использовать f-строки.

query = f'''SELECT * FROM table where column like '%%{my_value}%%' '''
cursor.execute(query)
person Tama Wilson    schedule 07.08.2020