Оператор LIKE и специальные символы в PDO/PHP

Я уже проверил ответы на такие вопросы, как это (Как мне создать параметризованный запрос PDO с оператором LIKE в PHP). Я пришел к этому решению:

$sql  = "SELECT count(*) ".
        "FROM mytable ".
        "WHERE num_certif LIKE CONCAT('%',:val,'%')";
$valeur = 'azert';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':val', $val);

Это работает, но вот моя проблема: как мне обрабатывать символ «%»? (т.е. $valeur = '%'; возвращает все строки)?


person Olivier Pons    schedule 28.04.2010    source источник


Ответы (5)


Вам нужно экранировать символ %,

  $valeur = '\%';
person ZZ Coder    schedule 28.04.2010
comment
Ага. Я знал это. Но если вы задумаетесь над принципом: prepare() и bindValue() были созданы как для оптимизации, и для предотвращения таких функций, как mysql_real_escape(), escape() и т.д. Поэтому, если мне нужно выполнить str_replace('%', '\%'...), это будет означать, что пропущена одна (важная) цель. Вот почему я искал элегантное решение, вместо того, чтобы возвращаться к старым временам php safe_mode. - person Olivier Pons; 29.04.2010
comment
Я не понял твоей мысли. Подготовленный оператор не имеет ничего общего с семантикой подстановочного знака. Подстановочный знак не должен менять своего значения при использовании в качестве аргументов для подготовленных операторов. - person ZZ Coder; 29.04.2010
comment
Я имею в виду: старый код = select * from xx, где t=.mysql_real_escape(blabla). ‹br /›новый код: подготовить (выбрать * из xx, где t=:tmp), затем bindValue(':tmp', $val). используя ваше предложение, это даст: bindValue(':tmp', str_replace('%', '\%', $val)). Это не чистый код для меня. Мы не должны использовать здесь функцию str_replace(). Должен быть другой способ, потому что этот способ является принципом экранирования нежелательных символов, который должен больше не существовать благодаря prepare() и bindValue(). Надеюсь, в этот раз я выразился более ясно :) - person Olivier Pons; 30.04.2010
comment
Итак, ваше решение работает хорошо, но это временное исправление, и я изо всех сил стараюсь избегать временных исправлений, потому что я их ненавижу. Но все равно спасибо, это поможет, если я не смогу найти чистое решение... - person Olivier Pons; 30.04.2010
comment
Привет, может я пропустил. Поэтому я постараюсь лучше объяснить свое p.o.v. : переменная $valeur может содержать нежелательные символы, такие как «%». Если это так, я должен изменить $valeur: я должен экранировать символы, такие как '%'. Если (я ошибаюсь, скажите мне). Таким образом, это означает, что я должен использовать такие вещи, как mb_strreplace() и так далее. Я пытаюсь сказать, что pdo был создан для многих вещей, включая безопасность. Безопасность с параметрами. Таким образом, вам никогда не придется экранировать или трогать переменные, которые вы передаете в свой запрос: вы выполняете prepare(), затем bind(), затем exec(). Вам все равно, что в $valeur. - person Olivier Pons; 08.12.2010
comment
Они сделали так, что вам никогда не придется заботиться о внедрении SQL. Вам никогда не нужно заботиться о том, что находится в ваших переменных... И если мне нужно избежать знака '%', это означает, что я должен заботиться о том, что находится в моей переменной (опять же, скажите мне, если я ошибаюсь здесь). Это означает, что со всеми запросами, которые я буду делать, мне придется изменить их в определенных случаях (где я хочу использовать операторы «как»). Таким образом, это означает, что ИМХО: необходимость экранирования (независимо от того, что такое char (здесь это «%»)) = нарушение части pdo, которая была сделана, чтобы избежать принудительного экранирования переменных в php-коде. - person Olivier Pons; 08.12.2010

Примечание для пользователей PostgreSQL... вместо функции CONCAT вы можете использовать

   SELECT count(*)
   FROM mytable
   WHERE num_certif LIKE '%' || :val || '%'
person Michael Butler    schedule 13.12.2012

Я добился этого, используя функции сравнения строк:

WHERE INSTR(LCASE(num_certif),LCASE(:val))>0

Подозреваю, что производительность пострадает.

person Jon Hulka    schedule 15.09.2012
comment
Ваш комментарий верен: производительность сильно страдает. :/ - person Olivier Pons; 17.09.2012
comment
@Olivier Pons Спасибо, что подтвердили это. В моем случае это не имеет большого значения, и я предпочитаю более элегантное решение. - person Jon Hulka; 18.09.2012

Я использовал что-то очень простое, например:

    $select_str = "SELECT * FROM table_x WHERE text_field LIKE '%".$value."%'";

    $StHandler = $this->dbHandler->prepare($select_str);
    $StHandler->execute();

Вы можете использовать только один % в зависимости от того, что вы ищете. Например, если вы хотите, чтобы оно начиналось с вашего значения и заканчивалось какими-либо символами позже, вы будете использовать "".$value."%'"

Надеюсь это поможет

person hmartinezd    schedule 24.12.2013

Чтобы избежать необходимости выполнять собственное экранирование, материал, который необходимо экранировать, должен быть частью данных, которые защищает pdo, а именно связанными аргументами. Это не защищает вас ни от чего в жестко закодированном запросе.

$sql  = "SELECT count(*) ".
    "FROM avs_souscript ".
    "WHERE num_certif =\"\" ".
    "AND date_annul=\"\" ".
    "AND user=:sess_user ".
    "AND user!=\"\" ".
    "AND num_certif LIKE :num_certif_search";
$valeur = 'azert'; //I assume this actually came from some user input
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':num_certif_search', '%'.$valeur.'%');

(или, в качестве альтернативы, вы можете поместить их в назначение $valuer = "%{$userInput}%";, в любом случае они должны быть в связанном аргументе, а не в sql.

Перемещение этого фрагмента глупой строки concat из sql в php также является хорошей практикой для создания масштабируемого приложения. Гораздо проще масштабировать ферму веб-серверов, чем масштабировать сервер базы данных.

person cabbey    schedule 08.12.2010
comment
Большое спасибо за ответ, чувак, но... Я сделал это, чтобы мне больше не нужно было заботиться о том, что находится в $valeur. С вашим кодом мне нужно заботиться о том, что находится в $ valeur. Предположим, что $valeur читается из POST и это только * char. Угадайте, что произойдет? Все строки возвращены. (Во всяком случае, это то, что происходит в моем примере кода, поэтому ваш ответ не является правильным решением). С вашим кодом мне пришлось бы экранировать символы, и мой вопрос звучит так: как избежать принудительного экранирования символов в коде php? Я не хочу изменять то, что приходит. pdo должен справиться с этим сам. - person Olivier Pons; 08.12.2010
comment
PDO защищает вас только от пользовательского ввода. Предполагается, что SQL является безопасным/нормальным. Если вам нужна защита (что такое побеги), защищаемые значения должны проходить через параметры. Мы не собираемся строить догадки о синтаксисе переменных с подстановочными знаками и о том, как интерпретировать то, что вы хотели сделать. Вам нужно сделать это. Вы, кажется, думаете, что PDO - это то, чем оно не является. - person cabbey; 08.12.2010
comment
Из руководства: PDO provides a data-access abstraction layer, which means that, regardless of which database you're using, you use the same functions to issue queries and fetch data. PDO does not provide a database abstraction; it doesn't rewrite SQL or emulate missing features. You should use a full-blown abstraction layer if you need that facility. - person cabbey; 08.12.2010