使用LIKE子句和NO_BACKSLASH_ESCAPES模式在mysqli prepare()中转义%登录

时间:2018-07-13 03:55:23

标签: php mysqli mariadb prepared-statement

问题:

注意:已启用NO_BACKSLASH_ESCAPES模式。

我正在使用mysqli准备的语句来查询数据库。目标代码如下:

$conn = new mysqli('localhost', 'root', 'mypass', 'mydb');
$stmt = $conn->prepare('select * from `table` where `data` like ?;');

现在,假设我手动(从mysql客户端而不是从php)插入3条记录,它们的文字值分别为JDxDDJD_DDJD\DD

INSERT INTO `table` SET `data` = 'JDxDD';
INSERT INTO `table` SET `data` = 'JD_DD';
INSERT INTO `table` SET `data` = 'JD\DD';

现在,考虑一下:

$stmt = $conn -> prepare('SELECT * from `table1` where `data` like ?;');
$key = 'JD_DD';
$stmt -> bind_param('s', $key);
$stmt-> execute();

这将按预期返回所有3行。现在,我只想选择一个值为JD_DD的值(即排除JDxDDJD\DD)。因此,正如回答here一样,我需要使用_关键字escape 转义 $key = 'JD\_DD';

但是,这:

$stmt = $conn -> prepare('SELECT * from `table1` where `data` like ? escape \'\\\';');
/*
first and last \ are for putting the ' quote,
middle 2 are for putting a \ as a part of string.
*/

将$ stmt评估为false,调用$conn->error会出现此错误:

  

您的SQL语法有错误;检查与您的MariaDB服务器版本相对应的手册以获取正确的语法,以在第1行的“ \”附近使用

以下两个代码均具有与上面相同的Syntax error

(1),我尝试切换到“”以封装查询,例如:

$stmt = $conn -> prepare("SELECT * from `table1` where `data` like ? escape '\\';");

(2)这是

$stmt = $conn -> prepare("SELECT * from `table1` where `data` like ? escape '\';");

请注意,$key = 'JD\_DD';尽管可以与LIKE运算符一起有效地使用(而不是=),但仅是一个示例。 $key可以很复杂,例如'JD\_D%\_X%';等。

我的努力:

以下是一些代码,可产生所需的输出:

(1)使用替代转义字符

$stmt = $conn -> prepare("SELECT * from `table1` where `data` like ? escape '|';");
$key = 'JD|_DD';
$stmt->bind_param('s', $key);

这可以正常工作。

(2) 完全省略escape一词:

$stmt = $conn -> prepare('SELECT * from `table1` where `data` like ?;');
$key = 'JD\_DD';
$stmt->bind_param('s', $key);

这将产生所需的JD_DD输出。 为什么?即使使用 NO_BACKSLASH_ESCAPES 模式 ENABLED ,为什么\仍用作转义符?

(3)以下内容也适用:

$stmt = $conn -> prepare("SELECT * from `table1` where `data` like ? escape '\\\';");
$key = 'JD\_DD';
$stmt->bind_param('s', $key);

(4),以下内容也是如此:

$stmt = $conn -> prepare("SELECT * from `table1` where `data` like ? escape '\\\\';");
$key = 'JD\_DD';
$stmt->bind_param('s', $key);

(3)和(4)产生相同的输出。同样,为什么?

(5)另外,我可以关闭NO_BACKSLASH_ESCAPES模式,但这似乎更像是解决方法,而不是解决方案。 / p>

mariadb docs说:

  

如果需要匹配字符_或%,则必须对它们进行转义。通过   默认情况下,您可以在通配符前面加上反斜杠字符   \以逃避它们。反斜杠用于编码特殊字符   字符串解析以及转义时的字符,例如换行符   解析后的模式中使用通配符。因此,要匹配实际   反斜杠,有时您需要将其两次转义为“ \\\\”。

     

为避免使用反斜杠字符造成麻烦,可以更改   在类似LIKE表达式中使用ESCAPE的通配符转义字符。的   ESCAPE的参数必须是一个单字符字符串。

此外,this SO post显示了一个在MariaDB 10.1.22中修复的错误。我正在使用MariaDB 10.1.13,并且该错误已修复在我的版本中。 (即select 'a%b' like '%\%\%';返回0 符合预期。)

我也经历过this mysql bug

非常感谢您帮助理解此问题的性质和起源,和/或在启用NO_BACKSLASH_ESCAPES模式的情况下为like子句编码的正确方法。

另外,这是我的第一个SO问题,因此我尝试尽可能地提供丰富信息和适当。任何对用更好的方式表达我的问题的批评家/建议也都表示赞赏。

谢谢!

编辑:

仅使用=而不是LIKE将无济于事。根据用户的偏好,可能有 个通配符。例如,如果用户喜欢根据他的输入(例如'J%D')获取所有相关建议,则该查询将变为WHERE data LIKE '%J\%D%';。如果用户只想获取他键入的内容,我肯定可以使用=代替LIKE

编辑2:

这是我做过的一些测试:

禁用了NO_BACKSLASH_ESCAPES模式

(1) INSERT INTO table set name = 'abc\def';

这将按预期插入abcdef

(2) INSERT INTO table set name = 'xyz\\pqr';

这将按预期插入xyz\pqr

(3) SELECT * FROM table where name = 'abc\def';

这将按预期返回abcdef

(4) SELECT * FROM table where name = 'abc\\def';

这将按预期返回0行。

(5) SELECT * FROM table where name = 'xyz\pqr';

这将按预期返回0行。

(6) SELECT * FROM table where name = 'xyz\\pqr';

这将按预期返回xyz\pqr

(7) SELECT * FROM table where name LIKE 'abc\def';

这将按预期返回abcdef

(8) SELECT * FROM table where name LIKE 'abc\\def';

这将返回abcdef不期望。 (8)是否仅在有任何abc\def时才显示?

(9) SELECT * FROM table where name LIKE 'xyz\pqr';

这将按预期返回0行。

(10) SELECT * FROM table where name LIKE 'xyz\\pqr';

这将返回0行。 不期望。 (10)是否应该显示xyz\pqr

现在启用了NO_BACKSLASH_ESCAPES模式 插入在(1)和(2)处的数据也会保留。

(11) INSERT INTO table set name = 'abc\def';

这将按预期插入abc\def

(12) INSERT INTO table set name = 'xyz\\pqr';

这将按预期插入xyz\\pqr

(13) SELECT * FROM table where name = 'abc\def';

这将按预期返回abc\def(插入11)。

(14) SELECT * FROM table where name = 'abc\\def';

这将按预期返回0行。

(15) SELECT * FROM table where name = 'xyz\pqr';

这将按预期返回xyz\pqr(插入2)。

(16) SELECT * FROM table where name = 'xyz\\pqr';

这将按预期返回xyz\\pqr(插入12)。

(17) SELECT * FROM table where name LIKE 'abc\def';

这将返回abcdef(在(1)处插入)。 不期望。启用此模式后,我希望它采用字面意义上的\(除非与escape = '\'一起使用)。预期结果为abc\def(插入11)。

(18) SELECT * FROM table where name LIKE 'abc\\def';

这将返回abc\def(在(11)处插入)。 不期望。启用此模式后,我希望它能同时使用\\。预期结果为0行,因为没有像abc\\def这样的数据。

(19) SELECT * FROM table where name LIKE 'xyz\pqr';

这将返回0行。 不期望。启用此模式后,我希望它采用\的字面意思。预期结果为xyz\pqr(插入2)。

(20) SELECT * FROM table where name LIKE 'xyz\\pqr';

这将返回xyz\pqr(在(11)处插入)。 不期望。启用此模式后,我希望它能同时使用\\。预期结果为xyz\\pqr(插入12)。


因此,总而言之,NO_BACKSLASH_ESCAPES模式按预期在INSERTWHERE col = value中运行-设置为ON时,逐字地获取每个\。 但是,使用WHERE col LIKE value时,它变得如上所示变得奇怪。

对于LIKE子句,即使将模式设置为“开”,也不能从字面上理解\

我现在强烈怀疑我对LIKE子句有一些非常基本的理解。任何澄清都非常欢迎!

2 个答案:

答案 0 :(得分:2)

只需使用=而不是LIKE ??

LIKE仅在您想要 %和/或_的通配符操作时使用。

修订案

要求:

  • 用户可以输入_,%或\-测试是按字面意义加上它们,
  • 用户输入的内容是字符串的开头

解决方案1避免处理LIKE

WHERE LEFT(data, CHAR_LENGTH($input)) = $input

解决方案2使LIKE正常工作而无需更改NO_BACKSLASH_ESCAPES

首先,将每个\增加四倍,第二个在每个_或%前面加上\。然后踩到%并执行LIKE

如果要求是$ input必须位于data中的任何位置,则

WHERE INSTR($input, data)

答案 1 :(得分:1)

更新14-07-2018:(解决方案)

正如@Progman的评论所指向的mysql docs,这是文档的摘要:

  

注意

     

因为MySQL在字符串中使用C转义语法(例如\n   代表换行符),则必须在其中使用的所有\都加倍   LIKE个字符串。例如,要搜索\n,请将其指定为\\n。至   搜索\,将其指定为\\\\;这是因为反斜杠是   一次解析器再次匹配模式   留下一个反斜杠来匹配。

     

异常:在模式字符串的末尾,反斜杠可以为   指定为\\。在字符串的末尾,反斜杠代表自身   因为没有追随者逃脱。

请注意,上面的摘要适用于禁用的NO_BACKSLASH_ESCAPES。

也就是说,这是我对此的简单理解:

LIKE子句中的字符串通过两件事:

(1)解析器

(2)模式匹配器

因此,当在 正常模式 中查找文字\时,我们必须 双倍转义 作为\\\\,一次用于 解析器 ,一次用于 模式匹配器 。< / p>

NO_BACKSLASH_ESCAPES模式 查找文字\时,我们可以 跳过转义 表示 解析器 (因为此模式使解析器将其视为 literal ),但我们< strong> 必须逃脱一次 ,即 模式匹配器 。因此,在这种模式下,我们必须使用\\来查找文字\

NO_BACKSLASH_ESCAPES模式不会影响模式匹配器!


总结:

(1)为获得相同的结果,普通模式下的斜杠比NO_BACKSLASH_ESCAPES模式下的斜杠要多 2x ,无论其在=LIKE中使用。这些是相同的:

SET @@sql_mode = '';
SELECT * FROM table where name LIKE 'abc\\\\def';

SET @@sql_mode = 'NO_BACKSLASH_ESCAPES';
SELECT * FROM table where name LIKE 'abc\\def';

两者均返回abc\def作为结果。

(2)为获得相同的结果,无论在正常模式下还是在NO_BACKSLASH_ESCAPES模式下使用,LIKE都需要比=高2倍的斜杠。这些是相同的:

SELECT * FROM table where name LIKE 'abc\\\\def';

SELECT * FROM table where name = 'abc\\def';

在正常模式下均返回abc\def ,在 NO_BACKSLASH_ESCAPES模式下均返回abc\\def

非常感谢,@ Progman!