尝试参数化SQL LIMIT时出现PHP PDO错误:offset,:display?

时间:2012-05-01 16:55:53

标签: php mysql pdo rowcount

此查询在phpMyAdmin中返回5个结果:

SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT 0,5

这会在phpMyAdmin中返回count = 12(这很好,因为有12条记录):

SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT 0,5

在添加两个变量(偏移,显示)之前,此功能正常工作,但是现在它不起作用,打印变量会给我偏移= 0,显示= 5(所以仍然是LIMIT 0,5 )。

function getProducts($offset, $display) {
    $sql = "SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT ?,?;";
    $data = array((int)$offset, (int)$display);
    $rows = dbRowsCount($sql, $data);
logErrors("getProducts(".$offset.",".$display.") returned ".$rows." rows.");
    if ($rows > 0) {
        dbQuery($sql, $data);
        return dbFetchAll();
    } else {
        return null;
    }
}

它不起作用,因为我的dbRowsCount(...)方法返回空字符串(愚蠢PDOStatement::fetchColumn),因此我将其更改为返回带有PDO::FETCH_ASSOC的计数,并返回count = 0。

这是执行行计数的函数:

function dbRowsCount($sql, $data) {
    global $db, $query;
    $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/is';
    if (preg_match($regex, $sql, $output) > 0) {
        $query = $db->prepare("SELECT COUNT(*) AS count FROM {$output[1]}");
logErrors("Regex output: "."SELECT COUNT(*) AS count FROM {$output[1]}");
        $query->setFetchMode(PDO::FETCH_ASSOC);
        if ($data != null) $query->execute($data); else $query->execute();
        if (!$query) {
            echo "Oops! There was an error: PDOStatement returned false.";
            exit;
        }
        $result = $query->fetch();
        return (int)$result["count"];
    } else {
logErrors("Regex did not match: ".$sql);
    }
    return -1;
}

我的错误日志为我提供了该程序的输出:

  

正则表达式输出:SELECT COUNT(*)AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT?,?;
  getProducts(0,5)返回0行。

正如您所看到的,SQL没有格式错误,方法输入变量按预期为0和5。

有谁知道出了什么问题?

更新

根据建议,我确实尝试直接执行查询,并返回正确的结果:

function dbDebugTest() {
    global $db;
    $stmt = $db->query("SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update LIMIT 0,5;");
    $result = $stmt->fetch();
    $rows = (int)$result["count"];
    logErrors("dbDebugTest() returned rows=".$rows);
}

输出:

> dbDebugTest() returned rows=12

根据另一个建议,我改变了!= null到!== null,我还打印出$ data数组:

logErrors("Data: ".implode(",",$data));
if ($data !== null) $query->execute($data); else $query->execute();

输出:

> Data: 0,5

但是,dbRowsCount($ sql,$ data)仍会为此查询返回0行!

更新2

遵循建议实现a custom PDOStatement class允许我在绑定值后输出查询,我发现函数将在$ query-> execute($ data)之后停止,因此输出将不打印,虽然自定义类适用于我的程序中的每个其他查询。

更新的代码:

function dbRowsCount($sql, $data) {
    global $db, $query;
    $regex = '/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/is';
    if (preg_match($regex, $sql, $output) > 0) {
        $query = $db->prepare("SELECT COUNT(*) AS count FROM {$output[1]}");
logErrors("Regex output: "."SELECT COUNT(*) AS count FROM {$output[1]}");
        $query->setFetchMode(PDO::FETCH_ASSOC);
logErrors("Data: ".implode(",",$data));
        $query->execute($data);
logErrors("queryString:".$query->queryString);
logErrors("_debugQuery():".$query->_debugQuery());
        if (!$query) {
            echo "Oops! There was an error: PDOStatement returned false.";
            exit;
        }
        $result = $query->fetch();
        return (int)$result["count"];
    } else {
logErrors("Regex did not match: ".$sql);
    }
    return -1;
}

输出:

  

正则表达式输出:SELECT COUNT()AS count FROM tbl_product_category WHERE id =?;
  数据:5
  queryString:SELECT COUNT(
)AS count FROM tbl_product_category WHERE id =?;
  _debugQuery():SELECT COUNT(*)AS count FROM tbl_product_category WHERE id =?;

     

正则表达式输出:SELECT COUNT(*)AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT?,?;
  数据:0,5
  //函数已停止且无法输出_debugQuery

更新3

由于我无法获得自定义PDOStatement类给我一些输出,我想我会重写getProducts(...)类来将params与命名占位符绑定。

function getProducts($offset, $display) {
    $sql = "SELECT * FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT :offset, :display;";
    $data = array(':offset'=>$offset, ':display'=>$display);
    $rows = dbRowsCount($sql, $data);
logErrors("getProducts(".$offset.",".$display.") returned ".$rows." rows.");
    if ($rows > 0) {
        dbQuery($sql, $data);
        return dbFetchAll();
    } else {
        return null;
    }
}

输出:

  

正则表达式输出:SELECT COUNT(*)AS count FROM tbl_product WHERE 1 ORDER BY last_update DESC LIMIT:offset,:display;
  数据:0,5
  //在$ query->执行($ data)后仍然崩溃,因此logErrors("getProducts(".$offset."...))未打印出来

更新4

此dbDebugTest以前曾在SQL字符串中直接声明限制值0,5。现在我已经更新它以正确绑定参数:

function dbDebugTest($offset, $display) {
    logErrors("Beginning dbDebugTest()");
    global $db;
    $stmt = $db->prepare("SELECT COUNT(*) AS count FROM tbl_product WHERE 1 ORDER BY last_update LIMIT :offset,:display;");
    $stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
    $stmt->bindParam(':display', $display, PDO::PARAM_INT);
    if ($stmt->execute()) {
      $result = $stmt->fetch();
      $rows = (int)$result["count"];
      logErrors("dbDebugTest() returned rows=".$rows);
    } else {
      logErrors("dbDebugTest() failed!");
    }
}

该功能崩溃,只输出:

  

开始dbDebugTest()

更新5

根据建议打开错误(默认情况下它们已关闭),我这样做了:

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

这本身就让Update 4的dbDebugTest()工作了!

  

开始dbDebugTest()   dbDebugTest()返回rows = 12

现在我的网络服务器日志中出现了错误:

  

[warn] mod_fcgid:stderr:PHP致命错误:
  带有消息'SQLSTATE [42000]的未捕获异常'PDOException':
  语法错误或访问冲突:1064 SQL语法中有错误;
  检查与MySQL服务器版本对应的手册,以获得正确的语法   在第1行''附近''0','5''附近   在/home/linweb09/b/example.com-1050548206/user/my_program/database/dal.php:36

第36行引用dbRowsCount(...)方法,行为$query->execute($data)

所以另一种方法getProducts(...)仍然不起作用,因为它使用这种绑定数据的方法,而params变成''0'和'5''(这是一个错误吗?)。有点烦人,但我必须在我的dal.php中创建一个新方法,以允许自己以更严格的方式绑定params - bindParam

特别感谢@ Travesty3和@eggyal的帮助!!非常感谢。

2 个答案:

答案 0 :(得分:1)

您正在测试$data NULL是否为NULL,而不是标识符运算符(有关===值的详细信息,请参阅the PHP manual由各种比较运营商处理)。您需要使用身份测试!== / {{1}},否则请致电is_null()

如上所述@ Travesty3,要测试数组是否为空,请使用empty()

答案 1 :(得分:1)

基于问题中的更新2,执行在execute语句之后停止,看起来查询失败。查看some PDO documentation后,看起来默认的错误处理设置是PDO :: ERRMODE_SILENT,这会导致您看到的行为。

这很可能是由于LIMIT子句中的数字在作为参数传入时被放入单引号中,如this post中所发生的那样。

该帖子的解决方案是使用bindValue方法将参数指定为整数。所以你可能不得不做类似的事情。

看起来您应该使用try-catch块执行查询以捕获MySQL错误。


bindValue方法:

if ($data !== null)
{
    for ($i=0; $i<count($data); $i++)
        $query->bindValue($i+1, $data[$i], PDO::PARAM_INT);
    $query->execute($data);
}
else
    $query->execute();