为什么此PDO参数化查询的行为“奇怪”?

时间:2019-09-27 13:26:16

标签: pdo

在这里,我已经编辑了原始问题。 我已经在下一封邮件中回答了。

我正在尝试通过参数化的php PDO查询从MySQL获取结果,但是事情表现得很糟糕。我不知道这是一个错误,还是我做错了什么或看不到明显的东西。

让我们假设数据库中有这两个表

CREATE TABLE `users` (
`user_id` int(11) NOT NULL PRIMARY KEY 
) 

CREATE TABLE `users_contacts` (
`contact_id` int(11) NOT NULL PRIMARY KEY ,
`user_id` int(11) DEFAULT NULL,
`type` varchar(45) DEFAULT NULL,
`value` varchar(255) DEFAULT NULL
) 

用最少的数据填充它们:

INSERT INTO `users` (`user_id`) VALUES (125);

INSERT INTO `users_contacts` (`contact_id`, `user_id`, `type`, `value`) 
    VALUES(11432, 125, 'email', 'losleyTyped@offten.stinks'),
          (11433, 125, 'phone', '1234567'),
          (564, 125, 'unit', '910');

然后尝试像这样获取数据

$db_name = "";
$db_user = "";
$db_pass = "";
$db_pdo  = new pdo("mysql:host=localhost;dbname=$db_name","$db_user","$db_pass");


$user          = 125;
$user_unit_btm = 900;
$user_unit_top = $user_unit_btm + 100;

$upload_user = $db_pdo -> prepare("SELECT K.value AS unit
                                    FROM users AS O, 
                                         users_contacts AS K 
                                    WHERE   O.user_id = :user_id AND 
                                            K.user_id = O.user_id AND 
                                            K.type = 'unit' AND 
                                            K.value >= :unit_btm AND  
                                            K.value < :unit_top
                                 ");


$upload_user -> execute( [":user_id"   => $user,
                          ":unit_btm"  => $user_unit_btm,
                          ":unit_top"  => $user_unit_top
                         ]
                       );


$upload_user = $upload_user -> fetch(PDO::FETCH_ASSOC);

var_dump($upload_user);

var_dump将返回false,但没有错误(错误为0000)

我减少了这个问题,发现只有一个参数“:organization”是有问题的,并且会导致bizare行为。

但是如果您替换“ K.value <:unit_top“ 带有变量$ user_unit_top
    “ K.value <$ user_unit_top”
然后,查询返回结果!

如果我用文字1000替换“ K.value <:unit_top”,也是一样,
    “ K.value <100”
然后查询返回结果!

为什么会这样?

2 个答案:

答案 0 :(得分:0)

这是一个答案,如果有人发现他自己也有类似问题

  

为什么会这样?
  我认为这是因为Php或MySQL身份类型识别/广播

看看表var FAQfor3Months = new Dictionary<string, int>(); var FAQfor4to12Months = new Dictionary<string, int>(); var FAQfor12plusMonths = new Dictionary<string, int>(); foreach (Activity call in CallsForProperty) { if ( /*call is in 3 months*/ ) { //do some unrelated stuff FAQfor3Months.TryGetValue(call.callQuestion, out int count); FAQfor3Months[call.callQuestion] = count+1; } } var top3ThreeMonthFaqs = FAQfor3Months.OrderByDescending(kvp => kvp.Value).Take(3).Select(kvp => kvp.Key).ToList(); ,名为“值”的列的类型为VARCHAR(因为该表类似于EAV数据模型)

在此行的问题中准备好的查询中

users_contacts

PHP / MySQL假设参数:unit_top与K.value列(VARCHAR)具有相同的数据类型,因此它像字符串???一样对它们进行比较-排序,字符串“ 910”后面是“ 1000”。 br /> 如果将变量“:unit_top”替换为变量$ user_unit_top

K.value < :unit_top  

或文字

K.value < $user_unit_top  

然后Php在进行数值比较,“ 1000”比“ 910”大

*如果将名为“值”的表K.value < 1000 列从VARCHAR更改为INT,则

users_contacts

比较将是数字

我认为这是非常不一致的行为,因为如果您进行以下操作

K.value < :unit_top  

PHP将执行自动类型转换,然后进行数字比较,...

我错了吗?

答案 1 :(得分:0)

如我对您的答案的评论中所述。

关于PDOStatement::execute的PHP文档。

  

一个值数组,其中元素的数量与正在执行的SQL语句中绑定的参数一样多。所有值都被视为PDO::PARAM_STR
  来源:https://www.php.net/manual/en/pdostatement.execute.php

如果没有更多结果或失败,则PDOStatement::fetch()返回false

  

此函数成功返回的值取决于访存类型。在所有情况下,FALSE将在失败时返回。

示例https://3v4l.org/NVECJ

$pdo = new \PDO('sqlite::memory:', null, null, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
]);
$pdo->query('CREATE TABLE foo(id INTEGER)');

$stmt = $pdo->prepare('SELECT * FROM foo');
$stmt->execute();
var_dump($stmt->fetch());
//bool(false)

如果您需要为发送给MySQL的参数明确定义数据类型(PDO::PARAM_STR除外),则可以使用PDOStatement::bindParamPDOStatement::bindValue

示例:

$upload_user = $db_pdo->prepare('SELECT 
        K.value AS unit
    FROM users AS O, 
        users_contacts AS K 
    WHERE O.user_id = :user_id 
    AND K.user_id = O.user_id
    AND K.type = \'unit\'
    AND K.value >= :unit_btm
    AND K.value < :unit_top');
$upload_user->bindValue(':user_id', $user, PDO::PARAM_INT);
$upload_user->bindValue(':unit_btm', $user_unit_btm, PDO::PARAM_INT);
$upload_user->bindValue(':unit_top', $user_unit_top, PDO::PARAM_INT);
$upload_user->execute();

另一种选择是强制对查询中的参数进行数据类型转换。

$upload_user = $db_pdo->prepare('SELECT 
        K.value AS unit
    FROM users AS O, 
        users_contacts AS K 
    WHERE O.user_id = :user_id
    AND K.user_id = O.user_id
    AND K.type = \'unit\' 
    AND K.value >= (:unit_btm - 0)
    AND K.value < (:unit_top - 0)'); //CAST(:unit_top AS SIGNED)
$upload_user->execute([
    ':user_id' => $user,
    ':unit_btm' => $user_unit_btm,
    ':unit_top' => $user_unit_top
]);

另一个导致您遇到问题的因素是,MySQL将自动转换为列的数据类型以进行比较。其他RDMBS(例如PostgreSQL和SQLite3)不执行相同的转换。

  

当运算符与不同类型的操作数一起使用时,请键入   进行转换以使操作数兼容。一些转换   隐式发生。例如,MySQL自动将字符串转换为   编号,反之亦然。
  来源:https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html

由于您的初始列数据类型为VARCHAR,因此测试得出了以下结果。
DB Fiddle

初始查询为PDOStatement::execute([1000])

SELECT IF('910' > '1000', 'fail', 'pass') AS if_str_to_str;

| if_str_to_str |
| ------------- |
| fail          |

手动为查询提供整数

SELECT IF('910' > 1000, 'fail', 'pass') AS if_str_to_int;

| if_str_to_int |
| ------------- |
| pass          |

更改数据库列数据类型并使用PDOStatement::execute([1000])

SELECT IF(910 > '1000', 'fail', 'pass') AS if_int_to_str;

| if_int_to_str |
| ------------- |
| pass          |

使用PDOStatement::bindValue(':param', '1000', PDO::PARAM_INT)('1000' - 0)

SELECT IF('910' > CAST('1000' AS SIGNED), 'fail', 'pass') AS if_str_to_typecast_int;

| if_str_to_typecast_int |
| ---------------------- |
| pass                   |