Почему этот параметризованный запрос PDO ведет себя "странно"? - PullRequest
0 голосов
/ 27 сентября 2019

Здесь я отредактировал свой оригинальный вопрос.Я также ответил на это в своем следующем сообщении.

Я пытаюсь получить результаты от MySQL с параметризованным запросом php PDO, но все ведет себя странно.Я не знаю, является ли это ошибкой, или я делаю что-то неправильно или не вижу чего-то очевидного.

Предположим, что в базе данных есть две эти таблицы

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)

Я уменьшил проблему и обнаружил, что только один параметр ": организация" проблематичен и вызывает странное поведение.

Но если вы замените "K.value <: unit_top" на переменную $ user_unit_top <br>"K.value <$ user_unit_top" <br>Затем запрос вернет результат!

То же самоеесли я заменю «K.value <: unit_top» на литерал 1000, <br>«K.value <100» <br>Тогда запрос вернет результат!

Почему это происходит?

Ответы [ 2 ]

0 голосов
/ 27 сентября 2019

Как уже упоминалось в моем комментарии к вашему ответу.

Документация PHP по PDOStatement::execute состояния.

Массив значений с таким количеством элементов, сколько связанных параметров в выполняемой инструкции 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)

Если вам необходимо явно определить тип данных, отличный от PDO::PARAM_STR дляпараметр, отправляемый в MySQL, вы будете использовать PDOStatement::bindParam или PDOStatement::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                   |
0 голосов
/ 27 сентября 2019

Вот ответ, если кто-то обнаружит, что у него есть аналогичная проблема

Почему это происходит?
Я думаю, это потому, что Php или MySQL распознает / приводит к автоматическому типу

Взгляд таблицы users_contacts столбец с именем 'value' имеет тип VARCHAR (потому что таблица похожа на модель данных EAV)

В подготовленном запросе из моего вопроса в этой строке

K.value < :unit_top  

PHP / MySQL предполагает, что этот параметр: unit_top имеет тот же тип данных, что и столбец K.value (VARCHAR), поэтому он сравнивает их как строки ???, а строка "910" позжезатем «1000».
Если вы замените параметр «: unit_top» на переменную $ user_unit_top

K.value < $user_unit_top  

или литерал

K.value < 1000  

Тогда Php выполняет числовое сравнение, и «1000 "больше, чем" 910 "

* Если вы измените столбец таблицы users_contacts с именем 'value' с VARCHAR на INT, тогда сравнение

K.value < :unit_top  

будет числовым

Я думаю, что это очень противоречивое поведениеиор, потому что если вы сделаете следующее

$a = "1000";  
$b = " 910";  
var_dump($a < $b);  

PHP выполнит автоматическое приведение типов и затем числовое сравнение, ...

я не прав?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...