PDO подготовил операторы для INSERT и ON DUPLICATE KEY UPDATE с именованными заполнителями

Я хотел бы переключить подготовленные операторы PDO INSERT и UPDATE на INSERT и ON DUPLICATE KEY UPDATE, поскольку я думаю, что это будет намного эффективнее, чем то, что я делаю сейчас, но у меня возникают проблемы с определением правильного синтаксиса для использовать с именованными заполнителями и bindParam.

Я нашел несколько похожих вопросов по SO, но я новичок в PDO и не смог успешно адаптировать код для своих критериев. Это то, что я пробовал, но не работает (не вставляет и не обновляет):

try { 
  $stmt = $conn->prepare('INSERT INTO customer_info (user_id, fname, lname) VALUES(:user_id, :fname, :lname)'          
 'ON DUPLICATE KEY UPDATE customer_info SET fname= :fname, 
                                            lname= :lname   
                                            WHERE user_id = :user_id'); 
  $stmt->bindParam(':user_id', $user_id);  
  $stmt->bindParam(':fname', $_POST['fname'], PDO::PARAM_STR);
  $stmt->bindParam(':lname', $_POST['lname'], PDO::PARAM_STR);      
  $stmt->execute();
}

Это упрощенная версия моего кода (у меня несколько запросов, и в каждом запросе от 20 до 50 полей). В настоящее время я сначала обновляю и проверяю, больше ли количество обновленных строк, чем 0, а если нет, то запускаю Insert, и каждый из этих запросов имеет собственный набор операторов bindParam.


person Chaya Cooper    schedule 30.12.2012    source источник
comment
Не используйте один и тот же заполнитель повторно в нескольких местах одного запроса. Установлено ли ваше соединение PDO для выдачи исключений? Если у вас нет реальной потребности в bindParam, bindValue или передача параметров через execute - лучший выбор.   -  person DCoder    schedule 30.12.2012
comment
Когда вы говорите, что мне не следует повторно использовать один и тот же заполнитель в нескольких местах, вы имеете в виду, что мне нужно иметь 2 набора операторов bindParam? Я установил его в настоящее время с помощью - catch (PDOException $ e) {echo 'Error:'. $ e- ›getMessage ();} - и я не получаю сообщений об ошибках для этого кода.   -  person Chaya Cooper    schedule 30.12.2012
comment
Недостаточно просто поставить try / catch вокруг запроса. См. Раздел Обработка ошибок и настройте его на создание исключений при ошибках.   -  person DCoder    schedule 30.12.2012
comment
@DCoder: Не могли бы вы уточнить, что вы имеете в виду? Я думал, что то, как я это делаю, соответствует ERRMODE_EXCEPTION в руководстве, которое должно вызывать исключение PDOException.   -  person Chaya Cooper    schedule 30.12.2012
comment
В вашем примере не показано, что установлен ERRMODE_EXCEPTION. Вот почему я спросил, настроено ли ваше соединение для этого. Если он у вас установлен, больше ничего не нужно, проблемы должны вызывать исключения.   -  person DCoder    schedule 30.12.2012
comment
@DCoder Плохо, что я не включил его ;-) У меня уходит немного времени, чтобы разобраться в PDO, и я забыл, что поместил часть обработки ошибок в файл конфигурации.   -  person Chaya Cooper    schedule 30.12.2012
comment
@DCoder: Я принял ваши предложения и переключил его на bindValue, но мне бы хотелось уточнить, что вы говорили о разнице между bindParam и bindValue и передаче параметров через execute. (Изначально мне не удавалось заставить код работать с bindValue, поэтому я использовал bindParam)   -  person Chaya Cooper    schedule 30.12.2012
comment
См. этот вопрос, чтобы узнать о различиях между bindParam и bindValue. $stmt->execute(array(':fname' => $_POST['fname'])) также можно использовать для передачи связанных переменных. На мой взгляд, execute - самый простой вариант, и ему следует отдавать предпочтение, если вам не нужны конкретные варианты поведения bindParam или bindValue. Конечно, ваш пробег может отличаться.   -  person DCoder    schedule 30.12.2012
comment
Спасибо за эту ссылку - она ​​так хорошо объяснила :-) У меня сложилось впечатление, что привязка значений или параметров более безопасна, чем простое выполнение массива. Я ошибаюсь?   -  person Chaya Cooper    schedule 30.12.2012


Ответы (2)


Ваш ON DUPLICATE KEY синтаксис неверен.

$stmt = $conn->prepare('INSERT INTO customer_info (user_id, fname, lname) VALUES(:user_id, :fname, :lname)
    ON DUPLICATE KEY UPDATE fname= :fname2, lname= :lname2');

$stmt->bindParam(':user_id', $user_id);  
$stmt->bindParam(':fname', $_POST['fname'], PDO::PARAM_STR);
$stmt->bindParam(':lname', $_POST['lname'], PDO::PARAM_STR);      
$stmt->bindParam(':fname2', $_POST['fname'], PDO::PARAM_STR);
$stmt->bindParam(':lname2', $_POST['lname'], PDO::PARAM_STR);      

Вам не нужно указывать имя таблицы или SET в предложении ON DUPLICATE KEY, и вам не нужно предложение WHERE (оно всегда обновляет запись с дублирующимся ключом).

См. http://dev.mysql.com/doc/refman/5.5/en/insert-on-duplicate.html

У вас также была синтаксическая ошибка PHP: вы разбили запрос на две строки.

ОБНОВИТЬ:

Чтобы связать несколько параметров:

function bindMultiple($stmt, $params, &$variable, $type) {
  foreach ($params as $param) {
    $stmt->bindParam($param, $variable, $type);
  }
}

Тогда назовите это:

bindMultiple($stmt, array(':fname', ':fname2'), $_POST['fname'], PDO::PARAM_STR);
person Barmar    schedule 30.12.2012
comment
Спасибо, что объяснили это так ясно :-) Есть ли способ обойтись без создания двух операторов bindParam для каждого параметра? - person Chaya Cooper; 30.12.2012
comment
Нет, PDO требует, чтобы каждый заполнитель был уникальным. Вы можете написать функцию, которая принимает массив заполнителей и переменную и вызывает bindParam() в цикле, чтобы связать их все. - person Barmar; 30.12.2012
comment
Ты классный :-D Я попробую утром первым делом :-) - person Chaya Cooper; 30.12.2012
comment
Первая часть кода работала как шарм, но мне не удается заставить ее работать с привязкой нескольких параметров. Чтобы уточнить, могу ли я создавать отдельные инструкции bindMultiple для каждого элемента? Кроме того, я не был уверен, правильно ли я думал, что мне следует оставить исходный оператор bindParam для user_id? - person Chaya Cooper; 30.12.2012
comment
Вам нужно использовать отдельный bindMultiple для каждой переменной, которую нужно повторить в SQL. Вам нужно повторить $_POST['fname'] и $_POST['lname'], но есть только один $user_id. - person Barmar; 30.12.2012
comment
Я только что заметил проблему. bindParam требует, чтобы переменная была ссылкой. Я просто изменил bindMultiple, чтобы отразить это. - person Barmar; 30.12.2012
comment
Рад, что вы это уловили :-D Теперь он работает отлично :-D Есть ли что-нибудь, что мне нужно сделать по-другому для bindValue? Кроме того, я предполагаю, что могу добавить дополнительные заполнители для других запросов (например, выборки), но мне было интересно, есть ли проблемы с производительностью и т.д., о которых я должен помнить? - person Chaya Cooper; 30.12.2012
comment
Для bindValue() нет смысла ссылаться на один и тот же столбец или переменную несколько раз, поэтому это не проблема. - person Barmar; 31.12.2012
comment
Я знаю, что это, вероятно, очень простой вопрос, но можете ли вы объяснить, почему не имеет смысла делать это с помощью bindValue? Я еще не совсем разбираюсь во всем этом. @DCoder прокомментировал, что bindValue будет лучшим выбором, чем bindValue (и я был рад, что ваш замечательный скрипт работал с обоими ;-)) У меня есть несколько запросов для каждого из полей (вставка и обновление, конечно, выборка и т. Д.) И поскольку есть несколько сотен полей, я надеялся упростить определение и привязку переменных - person Chaya Cooper; 31.12.2012
comment
Извините, моя ошибка, я думал о bindResults. Единственная разница с bindValue заключается в том, что ему не нужно использовать ссылку, но я думаю, что для него по-прежнему требуются уникальные заполнители. - person Barmar; 31.12.2012
comment
Спасибо за пояснение :-D Итак, просто для проверки, могу ли я использовать bindMultiple как для bindValue? Кроме того, я предполагаю, что могу добавить дополнительные заполнители для других запросов (например, выборки), но мне было интересно, есть ли проблемы с производительностью и т.д., о которых я должен помнить? - person Chaya Cooper; 31.12.2012
comment
Да, просто измените его, чтобы использовать обычный параметр вместо ссылки. Я не думаю, что есть какие-то проблемы с производительностью, в этом нет ничего особенного. - person Barmar; 31.12.2012

IMHO, приведенный ниже, является правильным ответом для тех, кто снова с этим сталкивается.
Примечание: этот оператор предполагает, что user_id является КЛЮЧОМ в таблице.

ЗАЯВЛЕНИЕ действительно было неверным, но принятый ответ не был полностью правильным.

Если вы вставляете и обновляете с использованием одних и тех же значений (и не обновляете другие значения), это исправленный псевдокод запроса:

try { 
    //optional if your DB driver supports transactions
    $conn->beginTransaction();

    $stmt = $conn->prepare('INSERT INTO customer_info (user_id, fname, lname) ' . 
                'VALUES(:user_id, :fname, :lname)' .
                'ON DUPLICATE KEY UPDATE fname=VALUES(fname), lname=VALUES(lname)');
    $stmt->bindParam(':user_id', $user_id);  
    $stmt->bindParam(':fname', $_POST['fname'], PDO::PARAM_STR);
    $stmt->bindParam(':lname', $_POST['lname'], PDO::PARAM_STR);      
    $stmt->execute();

    //again optional if on MyIASM or DB that doesn't support transactions
    $conn->commit();
} catch (PDOException $e) {
    //optional as above:
    $conn->rollback();

    //handle your exception here $e->getMessage() or something
}
person Ligemer    schedule 13.02.2014
comment
+1 Я согласен, что использовать VALUES() проще, когда нужно использовать параметры. Но, как побочный вопрос, вам не нужно разбивать строку в PHP, как в Java. Вы можете поместить многострочную строку в один набор кавычек. - person Bill Karwin; 13.02.2014
comment
@BillKarwin Правильно, но в нашей компании нам нравится заключать наши строки в 80 или 100 символов :-) - person Ligemer; 13.02.2014
comment
Очень элегантное решение, снова использующее VALUES() вместо дублирования имен полей без дополнительного суффикса. Вскоре он станет устаревшим, и его можно будет упростить, просто используя имя поля. mysql docs. - person Markus Zeller; 27.01.2021