如何在 LIMIT 子句中应用 bindValue 方法?

Posted

技术标签:

【中文标题】如何在 LIMIT 子句中应用 bindValue 方法?【英文标题】:How to apply bindValue method in LIMIT clause? 【发布时间】:2022-01-12 08:04:09 【问题描述】:

这是我的代码快照:

$fetchPictures = $PDO->prepare("SELECT * 
    FROM pictures 
    WHERE album = :albumId 
    ORDER BY id ASC 
    LIMIT :skip, :max");

$fetchPictures->bindValue(':albumId', $_GET['albumid'], PDO::PARAM_INT);

if(isset($_GET['skip'])) 
    $fetchPictures->bindValue(':skip', trim($_GET['skip']), PDO::PARAM_INT);    
 else 
    $fetchPictures->bindValue(':skip', 0, PDO::PARAM_INT);  


$fetchPictures->bindValue(':max', $max, PDO::PARAM_INT);
$fetchPictures->execute() or die(print_r($fetchPictures->errorInfo()));
$pictures = $fetchPictures->fetchAll(PDO::FETCH_ASSOC);

我明白了

您的 SQL 语法有错误; 检查对应的手册 您的 mysql 服务器版本 在 ''15', 15' 附近使用的正确语法 第 1 行

似乎 PDO 在 SQL 代码的 LIMIT 部分中为我的变量添加了单引号。我查了一下,发现了这个我认为相关的错误: http://bugs.php.net/bug.php?id=44639

这是我在看的吗?自 2008 年 4 月以来,此错误已打开! 在此期间我们应该做什么?

我需要建立一些分页,并且在发送sql语句之前需要确保数据是干净的,sql注入安全的。

【问题讨论】:

Here is a related quetion but with bindParam instead 在重复问题中值得注意的答案:Parametrized PDO query and `LIMIT` clause - not working [duplicate] (Aug 2013; by Bill Karwin) 【参考方案1】:

我记得以前遇到过这个问题。在将值传递给绑定函数之前将其转换为整数。我认为这可以解决它。

$fetchPictures->bindValue(':skip', (int) trim($_GET['skip']), PDO::PARAM_INT);

【讨论】:

谢谢!但是在 PHP 5.3 中,上面的代码抛出了一个错误,说“致命错误:不能通过引用传递参数 2”。它不喜欢在那里投射一个 int 。而不是(int) trim($_GET['skip']),试试intval(trim($_GET['skip'])) 如果有人提供解释为什么会这样......从设计/安全(或其他)的角度来看会很酷。 这仅在启用模拟预编译语句时有效。如果它被禁用,它将失败(它应该被禁用!) @Ross 我无法具体回答这个问题——但我可以指出,LIMIT 和 OFFSET 是在所有 PHP/MYSQL/PDO 疯狂袭击开发电路之后粘在上面的功能......事实上,我相信几年前是 Lerdorf 本人负责监督 LIMIT 的实施。不,它没有回答这个问题,但它确实表明它是一个售后附加组件,而且你知道它们有时能发挥多大的作用...... @Ross PDO 不允许绑定到值——而是变量。如果您尝试 bindParam(':something', 2) 您将遇到错误,因为 PDO 使用指向数字不能具有的变量的指针(如果 $i 是 2,您可以有一个指向 $i 但不是指向2号)。【参考方案2】:

最简单的解决方案是关闭仿真模式。您只需添加以下行即可完成此操作

$PDO->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );

另外,当creating a PDO connection 时,可以将此模式设置为构造函数参数。这可能是一个更好的解决方案,因为有些人报告他们的驱动程序不支持setAttribute() 函数。

它不仅可以解决您的绑定问题,还可以让您将值直接发送到execute(),这将使您的代码大大缩短。假设已经设置了仿真模式,整个事情将需要多达六行代码

$skip = isset($_GET['skip']) ? (int)trim($_GET['skip']) : 0;
$sql  = "SELECT * FROM pictures WHERE album = ? ORDER BY id LIMIT ?, ?";
$stmt  = $PDO->prepare($sql);
$stmt->execute([$_GET['albumid'], $skip, $max]);
$pictures = $stmt->fetchAll(PDO::FETCH_ASSOC);

【讨论】:

SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes...为什么对 me 来说从来没有这么简单 :) 虽然我确信这会让大多数人在那里,但就我而言,我最终得到了使用类似于接受的答案的东西。只是提醒未来的读者! @MatthewJohnson 是什么驱动程序? 我不确定,但在the manual 中显示PDO::ATTR_EMULATE_PREPARES Enables or disables emulation of prepared statements. Some drivers do not support native prepared statements or have limited support for them。这对我来说是一个新的,但我又刚刚开始使用 PDO。通常使用mysqli,但我想我会尝试拓宽我的视野。 @MatthewJohnson 如果您使用 PDO for mysql,驱动程序确实支持此功能。因此,由于某些错误,您收到此消息 如果您收到驱动程序支持问题消息,请再次检查您是否为语句 ($stm, $stmt) 而不是 pdo 对象调用 setAttribute【参考方案3】:

查看错误报告,以下可能有效:

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);

$fetchPictures->bindValue(':skip', (int)trim($_GET['skip']), PDO::PARAM_INT);  

但是您确定您传入的数据是正确的吗?因为在错误消息中,数字后面似乎只有 一个 引号(而不是用引号括起来的整数)。这也可能是您传入数据的错误。可以发print_r($_GET); 了解一下吗?

【讨论】:

''15',15'。第一个数字完全用引号括起来。第二个数字根本没有引号。所以是的,数据很好。【参考方案4】:

这只是摘要。 参数化 LIMIT/OFFSET 值有四个选项:

    如above 所述禁用PDO::ATTR_EMULATE_PREPARES

    这可以防止通过 ->execute([...]) 传递的值始终显示为字符串。

    切换到手动->bindValue(..., ..., PDO::PARAM_INT)参数填充。

    然而,这不如 ->execute list[] 方便。

    只需在此处创建一个例外,并在准备 SQL 查询时插入纯整数。

     $limit = intval($limit);
     $s = $pdo->prepare("SELECT * FROM tbl LIMIT $limit");
    

    选角很重要。更常见的是,->prepare(sprintf("SELECT ... LIMIT %d", $num)) 用于此类目的。

    如果您不使用 MySQL,而是使用 SQLite 或 Postgres;您也可以直接在 SQL 中强制转换绑定参数。

     SELECT * FROM tbl LIMIT (1 * :limit)
    

    同样,MySQL/MariaDB 不支持 LIMIT 子句中的表达式。还没有。

【讨论】:

我会使用 sprintf() 和 %d for 3,我会说它比使用变量更稳定。 是的,varfunc cast+interpolation 并不是最实用的例子。在这种情况下,我经常使用我懒惰的$_GET->int["limit"]【参考方案5】:

LIMIT :init, :end

您需要以这种方式绑定。如果你有类似$req->execute(Array()); 的东西,它不会工作,因为它会将PDO::PARAM_STR 转换为数组中的所有变量,而对于LIMIT,你绝对需要一个整数。 bindValue 或 BindParam 随意。

$fetchPictures->bindValue(':albumId', (int)$_GET['albumid'], PDO::PARAM_INT);

【讨论】:

【参考方案6】:

由于没有人解释为什么会发生这种情况,我正在添加一个答案。这样做的原因是因为您使用的是trim()。如果您查看trim 的PHP 手册,返回类型为string。然后,您尝试将其作为PDO::PARAM_INT 传递。解决此问题的几种方法是:

    使用filter_var($integer, FILTER_VALIDATE_NUMBER_INT) 确保您传递的是整数。 正如其他人所说,使用intval() 使用(int) 铸造 用is_int()检查它是否是一个整数

还有很多方法,但这基本上是根本原因。

【讨论】:

即使变量一直是整数也会发生这种情况。【参考方案7】:

使用 PDO::PARAM_INT 的绑定值偏移和限制,它将起作用

【讨论】:

【参考方案8】:

//之前(当前错误) $query = " .... LIMIT :p1, 30;"; ... $stmt->bindParam(':p1', $limiteInferior);

//之后(已纠正错误) $query = " .... LIMIT :p1, 30;"; ... $limiteInferior = (int)$limiteInferior; $stmt->bindParam(':p1', $limiteInferior, PDO::PARAM_INT);

【讨论】:

【参考方案9】:

PDO::ATTR_EMULATE_PREPARES 给了我

驱动不支持此功能:此驱动不支持 设置属性错误。

我的解决方法是将$limit 变量设置为字符串,然后将其组合到prepare 语句中,如下例所示:

$limit = ' LIMIT ' . $from . ', ' . $max_results;
$stmt = $pdo->prepare( 'SELECT * FROM users WHERE company_id = :cid ORDER BY name ASC' . $limit . ';' );
try 
    $stmt->execute( array( ':cid' => $company_id ) );
    ...

catch ( Exception $e ) 
    ...

【讨论】:

【参考方案10】:

在不同版本的 PHP 和 PDO 的古怪之处之间发生了很多事情。 我在这里尝试了 3 或 4 种方法,但无法让 LIMIT 工作。 我的建议是使用带有intval() 过滤器的字符串格式化/连接:

$sql = 'SELECT * FROM `table` LIMIT ' . intval($limitstart) . ' , ' . intval($num).';';

使用 intval() 来防止 SQL 注入非常重要,尤其是当您从 $_GET 等获得限制时。 如果您这样做,这是获得 LIMIT 的最简单方法工作。

有很多关于“PDO 中 LIMIT 的问题”的讨论,但我的想法是 PDO 参数永远不会用于 LIMIT,因为它们总是整数,快速过滤器可以工作。尽管如此,这还是有点误导,因为其理念一直是不要自己进行任何 SQL 注入过滤,而是“让 PDO 处理它”。

【讨论】:

以上是关于如何在 LIMIT 子句中应用 bindValue 方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何加快 LIMIT 子句中偏移量较大的 MySQL 查询?

如何改进 MySQL 中的 Limit 子句

如何在 PDOStatement::bindValue() 中定义变量类型?

如何将 pdo 的准备好的语句用于 order by 和 limit 子句?

如何对包含子句“order by”、“desc”和“Limit”的多个列使用 distinct

MySQL的Limit详解