правильный способ избежать %% при построении запросов LIKE в Rails 3/ActiveRecord

Я хочу сопоставить поле URL-адреса с префиксом URL-адреса (который может содержать знаки процента), например. .where("url LIKE ?", "#{some_url}%"). Каков самый путь Rails?


person Costa Shapiro    schedule 18.04.2011    source источник
comment
Надеюсь, у вас нет планов масштабироваться до миллионов постов. Этот запрос будет высасывать системные ресурсы быстрее, чем внедорожник сливает бензин из бака.   -  person Wes    schedule 19.04.2011
comment
@Wes: зависит от вашей базы данных. Насколько я знаю, последние версии PostgreSQL могут использовать индекс для совпадений LIKE, в которых используется префикс (то есть формы X% для некоторого фиксированного X). Здесь есть некоторые примечания по этому поводу: stackoverflow.com/ вопросы/1566717/   -  person mu is too short    schedule 19.04.2011
comment
@Wes: Тем не менее, у вас все еще есть шаблоны %X%, которые почти наверняка дадут вам сканирование таблицы. Извините, что у меня нет авторитетной ссылки, но вы, вероятно, могли бы составить план запроса и посмотреть, что произойдет.   -  person mu is too short    schedule 19.04.2011


Ответы (4)


Если я правильно понимаю, вас беспокоит появление "%" внутри some_url, и это правильно; вам также следует беспокоиться о встроенных символах подчеркивания ("_"), они являются LIKE версией "." в регулярном выражении. Я не думаю, что есть какой-то специфичный для Rails способ сделать это, поэтому у вас остается gsub:

.where('url like ?', some_url.gsub('%', '\\\\\%').gsub('_', '\\\\\_') + '%')

Здесь также нет необходимости в интерполяции строк. Вам нужно удвоить обратную косую черту, чтобы скрыть их значение от синтаксического анализатора строк базы данных, чтобы синтаксический анализатор LIKE увидел простой «\%» и знал, что нужно игнорировать экранированный знак процента.

Вы должны проверить свои журналы, чтобы убедиться, что две обратные косые черты проходят. Я получаю запутанные результаты при проверке вещей в irb, использование пяти (!) дает правильный вывод, но я не вижу в этом смысла; если кто-то увидит смысл в пяти из них, будет признателен за пояснительный комментарий.

ОБНОВЛЕНИЕ: Джейсон Кинг любезно предложил упростить кошмар сбежавших побегов. Это позволяет указать временный escape-символ, чтобы вы могли выполнять определенные действия. нравится:

.where("url LIKE ? ESCAPE '!'", some_url.gsub(/[!%_]/) { |x| '!' + x })

Я также переключился на блочную форму gsub, чтобы сделать ее немного менее неприятной.

Это стандартный синтаксис SQL92, поэтому он будет работать в любой БД, которая его поддерживает, включая PostgreSQL, MySQL и SQLite.

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

person mu is too short    schedule 18.04.2011
comment
Кстати, если (я не знаю) вам нужно получить двойную обратную косую черту в этих строках, вышеприведенное этого не сделает. `'\\' =› ` в Ruby (и большинстве языков). - person smathy; 19.04.2011
comment
@Jason: пять побегов дают правильную вещь в irb, спасибо, что поймали это. Я предполагаю, что он проходит через три токена \\, \\ и \%, которые после обработки побега заканчиваются как \\% по желанию. Обычно я просто добавляю их, пока это не сработает, а затем реконструирую обоснование. Кто-нибудь знает оператор кавычек, который действительно выдает буквальные результаты без какой-либо обработки или большого беспорядка? - person mu is too short; 19.04.2011
comment
Что-то еще, что вы могли бы сделать, это добавить предложение ESCAPE, например: .where( "url LIKE ? ESCAPE '!'", some_url.gsub('%', '!%').gsub('_', '!_') - person smathy; 19.04.2011
comment
@Jason: Это гениально. Хотел бы я дать вам баллы за комментарий или просто передать вам право собственности на ответ. - person mu is too short; 19.04.2011
comment
@mu К сожалению, ваш ответ, вероятно, правильный. Единственная (едва убедительная) причина такой неприятности, о которой я могу думать: запросы LIKE отстой, и их обычно не следует использовать. - person Costa Shapiro; 19.04.2011
comment
@Costa: Спасибо за исправление. Иногда у вас есть выбор между различными типами боли: использовать LIKE или SIMILAR TO внутри базы данных и разбираться с чепухой цитирования или вытащить кучу материала из базы данных, выполнить сопоставление с образцом в Ruby, а затем выбросить большую часть данных. прочь. Шесть одних, полдюжины других. - person mu is too short; 19.04.2011
comment
@mu - добро пожаловать - и не беспокойтесь об очках, в конце концов, эти вещи имеют свойство решаться сами собой. - person smathy; 20.04.2011
comment
Вы также можете использовать группы захвата вместо синтаксиса блока (короче и, вероятно, быстрее): .where("url LIKE ? ESCAPE '!'", some_url.gsub(/([!%_])/, '!\1') - person RecursivelyIronic; 12.09.2012
comment
@RecursivelyIronic: я редко использую эту форму gsub, двойное значение обратной косой черты может быть проблематичным, поэтому я обычно просто использую блочную форму при работе с непостоянной заменой, чтобы мне не приходилось беспокоиться о том, когда нужно использовать обратную косую черту. быть удвоены и когда они не делают. Но это всего лишь личные предпочтения. - person mu is too short; 12.09.2012

Начиная с версии 4.2.x Rails существует активный метод записи с именем sanitize_sql_like. Итак, вы можете сделать в своей модели область поиска, например:

scope :search, -> search { where('"accounts"."name" LIKE ?', "#{sanitize_sql_like(search)}%") }

и вызовите область, например:

Account.search('Test_%')

Результирующая экранированная строка sql:

SELECT "accounts".* FROM "accounts" WHERE ("accounts"."name" LIKE 'Test\_\%%')

Подробнее читайте здесь: http://edgeapi.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html

person phlegx    schedule 23.08.2015
comment
На мой взгляд, это самый элегантный способ решить эту проблему. - person Nikola M.; 14.12.2015
comment
Угадайте с рельсов 5. * sanitize_sql_like не поддерживается. Любая другая альтернатива? - person Luna Lovegood; 11.12.2018
comment
Он поддерживается в Rails 5.x. См. здесь github.com/ рельсы/рельсы/blob/v5.2.1.1/activerecord/lib/ - person phlegx; 11.12.2018
comment
К вашему сведению, это защищенные/частные методы в 5.0 и 5.1. Они общедоступны только на 5.2. - person Teoulas; 05.07.2019
comment
@ Теулас прав. Rails 5.0 и 5.1, используйте для их вызова: ActiveRecord::Base.send(:sanitize_sql_like, "Test_%") - person stwr667; 08.09.2020

https://gist.github.com/3656283

С этим кодом

Item.where(Item.arel_table[:name].matches("%sample!%code%"))

корректно экранирует % между "sample" и "code" и соответствует "AAAsample%codeBBB", но не для "AAAsampleBBBcodeCCC" по крайней мере в MySQL, PostgreSQL и SQLite3.

person kaznum    schedule 06.09.2012

person    schedule
comment
@ Сэм Очень близко. Post.where('url like ?', "%#{some_url}%") - person Rein Henrichs; 19.04.2011
comment
Ваш код просто заставляет подобное условие соответствовать другим записям. @Costa-Shaprio ищет способ делать то, что вы делаете, плюс иметь % или, по крайней мере, так я это читаю, то есть «который может содержать знаки процента» - person thenengah; 19.04.2011
comment
@Rein: Нет. Это меняет характер совпадения (начинается с становится в любом месте) и ничего не делает для учета встроенного% в some_url, не говоря уже о подчеркивании. - person mu is too short; 19.04.2011
comment
@mu Хорошо, тогда удали первый %. Это простое недопонимание требований. Это все еще работает для случая 90%. И, по крайней мере, это синтаксически правильно, а "%#{some_url + '%'}% нет. - person Rein Henrichs; 19.04.2011
comment
@mu Вы проверяли, не экранирует ли уже %s санитизация SQL ActiveRecord, прежде чем прокомментировать? - person Rein Henrichs; 19.04.2011
comment
@Rein: Как AR отличит преднамеренное %x%x% от случайного "%#{a}%", когда a просто оказывается неэкранированным 'x%x'? В первом (преднамеренном) случае требуется что угодно x что угодно x что угодно, тогда как во втором случае требуется что угодно x%x < i>что угодно но получить что угодно x что угодно x что угодно, потому что никто не избежал этого. К тому времени, когда AR увидит аргумент LIKE, у него будет только строка со встроенным синтаксисом SQL, и уже слишком поздно делать выводы о намерениях программиста. - person mu is too short; 19.04.2011
comment
@ReinH - это не так, %s не экранируется AR - и нигде в AR нет % escaper. - person smathy; 19.04.2011
comment
Да, я понимаю, что означает % и почему важно его избежать. Я просто пытался исправить синтаксическую ошибку. Пожалуйста, направьте свои исправления в исходный ответ. - person Rein Henrichs; 19.04.2011
comment
Суть в том, что ответ не является хорошим. он не будет экранировать %s в URL-адресе. Я не могу поверить, что у рельсов нет нативного ответа на этот вопрос. что? - person Vitaly Kushner; 19.04.2011
comment
да, это не хорошо. Можете ли вы привести примеры ваших URL-адресов, которые вам нужны для поиска? Вот почему мой ответ отстой, я просто вставил код, который был как бы связан с вашим ответом. Опубликуйте несколько примеров URL-адресов, которые вы хотите найти. - person thenengah; 19.04.2011