как вставить массив формы в таблицу mysql, используя php? [Дубликат]

Проверьте его на SQL Server:

Select top 10 * From emp 
EXCEPT
Select top 9 * From emp

Это даст вам 10-ю строку таблицы emp!

120
задан Francisco de la Peña 28 April 2015 в 06:53
поделиться

20 ответов

Вставка нескольких значений с помощью подготовленных заявлений PDO

Вставка нескольких значений в один оператор выполнения. Почему, потому что согласно этой странице она быстрее, чем обычные вставки.

$datafields = array('fielda', 'fieldb', ... );

$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);
$data[] = array('fielda' => 'value', 'fieldb' => 'value' ....);

больше значений данных или у вас, вероятно, есть цикл, который заполняет данные.

С подготовленными вставками вам нужно знать введенные вами поля и количество полей создать? заполнители для привязки ваших параметров.

insert into table (fielda, fieldb, ... ) values (?,?...), (?,?...)....

Это в основном то, как мы хотим, чтобы выражение insert выглядело.

Теперь код:

function placeholders($text, $count=0, $separator=","){
    $result = array();
    if($count > 0){
        for($x=0; $x<$count; $x++){
            $result[] = $text;
        }
    }

    return implode($separator, $result);
}

$pdo->beginTransaction(); // also helps speed up your inserts.
$insert_values = array();
foreach($data as $d){
    $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
    $insert_values = array_merge($insert_values, array_values($d));
}

$sql = "INSERT INTO table (" . implode(",", $datafields ) . ") VALUES " .
       implode(',', $question_marks);

$stmt = $pdo->prepare ($sql);
try {
    $stmt->execute($insert_values);
} catch (PDOException $e){
    echo $e->getMessage();
}
$pdo->commit();

Хотя в моем тесте разница в 1 с была при использовании нескольких вставок и регулярных подготовленных вставок с единственным значением.

119
ответ дан Uwe Keim 22 August 2018 в 13:06
поделиться
  • 1
    Опечатка, в приведенном выше объяснении упоминает $ datafields, хотя $ datafield используется в $ sql. Таким образом, копирование пасты приведет к ошибке. Пожалуйста, исправьте. Спасибо за это решение. – pal4life 14 February 2012 в 02:48
  • 2
    Использовал это некоторое время, а затем заметил, что значения с одинарными кавычками в них не сбрасываются должным образом. Использование двойных кавычек на имплозии работает как прелесть для меня: $ a [] = '(& quot ;. ". Implode (& quot ;, & quot; $ question_marks).' & Quot ;, NOW ()) '; – qwertzman 24 September 2012 в 12:53
  • 3
    array_merge кажется дороже, чем просто использование array_push. – K2xL 17 October 2013 в 19:22
  • 4
    Когда вы говорите «было только различие в 1 сек», сколько строк вы вводили данные? 1 сек довольно значительна в зависимости от контекста. – Kevin Dice 2 June 2015 в 23:03
  • 5
    Оптимизация: не нужно снова звонить placeholders() снова. Вызовите его один раз перед циклом с sizeof($datafields) и добавьте строку результата в $question_marks[] внутри цикла. – AVIDeveloper 28 June 2016 в 07:44

Это сработало для меня

    $sql = 'INSERT INTO table(pk_pk1,pk_pk2,date,pk_3) VALUES '; 
    $qPart = array_fill(0, count($array), "(?, ?,UTC_TIMESTAMP(),?)");
 $sql .= implode(",", $qPart);
 $stmt =    DB::prepare('base', $sql);
     $i = 1;
     foreach ($array as $value) 
       { 
       $stmt->bindValue($i++, $value);
       $stmt->bindValue($i++, $pk_pk1);
       $stmt->bindValue($i++, $pk_pk2); 
      $stmt->bindValue($i++, $pk_pk3); 
      } 
    $stmt->execute();
0
ответ дан Andre Da Silva Poppi 22 August 2018 в 13:06
поделиться

Поскольку он еще не был предложен, я уверен, что LOAD DATA INFILE по-прежнему является самым быстрым способом загрузки данных, поскольку он отключает индексирование, вставляет все данные, а затем снова включает индексы - все в одном запросе.

Сохранение данных в виде csv должно быть довольно тривиальным, имея в виду fputcsv. MyISAM работает быстрее, но вы все равно получаете большую производительность в InnoDB. Есть и другие недостатки, хотя я бы пошел по этому маршруту, если вы вставляете много данных и не беспокоитесь о том, что у вас меньше 100 строк.

1
ответ дан avatarofhope2 22 August 2018 в 13:06
поделиться

Вы можете вставить несколько строк в один запрос с помощью этой функции:

function insertMultiple($query,$rows) {
    if (count($rows)>0) {
        $args = array_fill(0, count($rows[0]), '?');

        $params = array();
        foreach($rows as $row)
        {
            $values[] = "(".implode(',', $args).")";
            foreach($row as $value)
            {
                $params[] = $value;
            }
        }

        $query = $query." VALUES ".implode(',', $values);
        $stmt = $PDO->prepare($query);
        $stmt->execute($params);
    }
}

$ row - массив массивов значений. В вашем случае вы вызовете функцию с помощью

insertMultiple("INSERT INTO tbl (`key1`,`key2`)",array(array('r1v1','r1v2'),array('r2v1','r2v2')));

. Это имеет преимущество, которое вы используете подготовленные операторы , вставляя несколько строк с одним запросом. Безопасность!

0
ответ дан Chris Michaelides 22 August 2018 в 13:06
поделиться

Более короткий ответ: сгладьте массив данных, упорядоченных по столбцам, затем

//$array = array( '1','2','3','4','5', '1','2','3','4','5');
$arCount = count($array);
$rCount = ($arCount  ? $arCount - 1 : 0);
$criteria = sprintf("(?,?,?,?,?)%s", str_repeat(",(?,?,?,?,?)", $rCount));
$sql = "INSERT INTO table(c1,c2,c3,c4,c5) VALUES$criteria";

. При вставке 1000 или около того записей вам не нужно будет перебирать каждую запись, чтобы вставить их, когда все вам нужно подсчитать значения.

6
ответ дан fyrye 22 August 2018 в 13:06
поделиться

Для того, что стоит, я видел, что многие пользователи рекомендуют выполнять итерацию с помощью инструкций INSERT вместо того, чтобы строить как один строковый запрос, как это сделал выбранный ответ. Я решил запустить простой тест только с двумя полями и очень простой оператор insert:

<?php
require('conn.php');

$fname = 'J';
$lname = 'M';

$time_start = microtime(true);
$stmt = $db->prepare('INSERT INTO table (FirstName, LastName) VALUES (:fname, :lname)');

for($i = 1; $i <= 10; $i++ )  {
    $stmt->bindParam(':fname', $fname);
    $stmt->bindParam(':lname', $lname);
    $stmt->execute();

    $fname .= 'O';
    $lname .= 'A';
}


$time_end = microtime(true);
$time = $time_end - $time_start;

echo "Completed in ". $time ." seconds <hr>";

$fname2 = 'J';
$lname2 = 'M';

$time_start2 = microtime(true);
$qry = 'INSERT INTO table (FirstName, LastName) VALUES ';
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?), ";
$qry .= "(?,?)";

$stmt2 = $db->prepare($qry);
$values = array();

for($j = 1; $j<=10; $j++) {
    $values2 = array($fname2, $lname2);
    $values = array_merge($values,$values2);

    $fname2 .= 'O';
    $lname2 .= 'A';
}

$stmt2->execute($values);

$time_end2 = microtime(true);
$time2 = $time_end2 - $time_start2;

echo "Completed in ". $time2 ." seconds <hr>";
?>

. В то время как общий запрос сам занимал миллисекунды или меньше, последний (однострочный) запрос был последовательно в 8 раз быстрее или больше. Если бы это было построено, чтобы сказать, отражают импорт тысяч строк на многих столбцах, разница может быть огромной.

36
ответ дан JM4 22 August 2018 в 13:06
поделиться
  • 1
    хорошо доказано – mireille raad 4 August 2013 в 00:03
  • 2
    1 upvote для краткости и четкости – Stephen Adelakun 11 December 2015 в 16:24
  • 3
    @ JM4 - отличная идея поставить 10 строк непосредственно в одно исполнение . Но как я могу вставлять тысячи строк, когда они хранятся в объекте вроде JSON? Мой код ниже работает perferctly. Но как я могу настроить его, чтобы вставить 10 строк в одно исполнение? `foreach ($ json_content as $ datarow) {$ id = $ datarow [id]; $ date = $ datarow [date]; $ row3 = $ datarow [row3]; $ row4 = $ datarow [row4]; $ row5 = $ datarow [row5]; $ row6 = $ datarow [row6]; $ row7 = $ datarow [row7]; // теперь выполняем $ databaseinsert- & gt; execute (); } // конец foreach ` – Peter 14 September 2017 в 15:57
  • 4
    @ JM4 - ... и мой второй вопрос: «почему нет инструкции bind_param во второй процедуре импорта»? – Peter 14 September 2017 в 17:39
  • 5
    Разве вам не пришлось бы петли дважды? Вам также придется динамически генерировать (?,?), правильно? – NoobishPro 4 February 2018 в 03:26

Несмотря на то, что старый вопрос мне очень помог, это мое решение, которое работает в моем классе DbContext. Параметр $rows представляет собой просто массив ассоциативных массивов, представляющих строки или модели: field name => insert value.

Если вы используете шаблон, который использует модели, это прекрасно вписывается при передаче данных модели в виде массива, скажем, из метод ToRowArray в классе модели.

Примечание. Это должно быть само собой разумеющимся, но никогда не позволять аргументам, переданным этому методу, подвергаться воздействию пользователя или полагаться на любой пользовательский ввод, другое чем значения вставки, которые были проверены и дезинфицированы. Аргумент $tableName и имена столбцов должны быть определены логикой вызова; например, модель User может быть сопоставлена ​​с таблицей пользователя, которая имеет список столбцов, сопоставленных с полями членов модели.

public function InsertRange($tableName, $rows)
{
    // Get column list
    $columnList = array_keys($rows[0]);
    $numColumns = count($columnList);
    $columnListString = implode(",", $columnList);

    // Generate pdo param placeholders
    $placeHolders = array();

    foreach($rows as $row)
    {
        $temp = array();

        for($i = 0; $i < count($row); $i++)
            $temp[] = "?";

        $placeHolders[] = "(" . implode(",", $temp) . ")";
    }

    $placeHolders = implode(",", $placeHolders);

    // Construct the query
    $sql = "insert into $tableName ($columnListString) values $placeHolders";
    $stmt = $this->pdo->prepare($sql);

    $j = 1;
    foreach($rows as $row)
    {
        for($i = 0; $i < $numColumns; $i++)
        {
            $stmt->bindParam($j, $row[$columnList[$i]]);
            $j++;
        }
    }

    $stmt->execute();
}
1
ответ дан Lee 22 August 2018 в 13:06
поделиться
  • 1
    Почему нисходящий? – Lee 19 February 2017 в 10:57
  • 2
    избавиться от транзакции, поскольку нет смысла использовать ее для одного запроса. и, как обычно, этот код уязвим для SQL-инъекции или ошибки запроса. – Your Common Sense 19 February 2017 в 11:57
  • 3
    Вы правы в отношении избыточного использования транзакций для этого случая, но я не вижу, как это уязвимо для SQL-инъекции. Он параметризирован, поэтому я могу только предположить, что вы предполагаете, что $tableName открыт для пользователя, а это не так, это в DAL. Можете ли вы расширить свои претензии? Нехорошо говорить вещи. – Lee 19 February 2017 в 13:08
  • 4
    ну, это не только имя таблицы, но так или иначе: как вы узнаете, будет ли она открыта или нет всем, кто будет использовать код, который вы разместили здесь? – Your Common Sense 19 February 2017 в 13:12
  • 5
    Таким образом, ответственность за плакат заключается в том, чтобы описать каждое потенциальное использование кода или каждого источника для аргументов? Может быть, у меня более высокие ожидания людей. Это сделает вас счастливее, если я добавлю примечание, не позволяющее пользователю иметь доступ к $tableName? – Lee 19 February 2017 в 13:19

Принятый ответ Герберта Балагтаса хорошо работает, когда массив $ data мал. При больших массивах данных функция array_merge становится слишком медленной. Мой тестовый файл для создания массива $ data имеет 28 столбцов и составляет около 80 000 строк. Окончательный скрипт занял 41 место.

Использование array_push () для создания $ insert_values ​​вместо array_merge () привело к 100-кратной скорости с временем выполнения 0,41 с.

Проблемная array_merge ():

$insert_values = array();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
 $insert_values = array_merge($insert_values, array_values($d));
}

Чтобы устранить необходимость в array_merge (), вместо этого вы можете построить следующие два массива:

//Note that these fields are empty, but the field count should match the fields in $datafields.
$data[] = array('','','','',... n ); 

//getting rid of array_merge()
array_push($insert_values, $value1, $value2, $value3 ... n ); 

Затем эти массивы можно использовать следующим образом :

function placeholders($text, $count=0, $separator=","){
    $result = array();
    if($count > 0){
        for($x=0; $x<$count; $x++){
            $result[] = $text;
        }
    }

    return implode($separator, $result);
}

$pdo->beginTransaction();

foreach($data as $d){
 $question_marks[] = '('  . placeholders('?', sizeof($d)) . ')';
}

$sql = "INSERT INTO table (" . implode(",", array_keys($datafield) ) . ") VALUES " . implode(',', $question_marks);

$stmt = $pdo->prepare ($sql);
try {
    $stmt->execute($insert_values);
} catch (PDOException $e){
    echo $e->getMessage();
}
$pdo->commit();
27
ответ дан Luke 22 August 2018 в 13:06
поделиться
  • 1
    В PHP 5.6 вы можете сделать array_push($data, ...array_values($row)) вместо $data = array_merge($data, array_values($row));. Намного быстрее. – mpen 5 February 2015 в 19:08
  • 2
    Почему 5,6? Документация ничего не говорит о 5.6, array_push() доступна даже в php 4. – ZurabWeb 20 September 2015 в 20:02
  • 3
    @Piero это PHP 5.6+ только код не из-за использования array_push(), а потому, что @Mark использует распаковку аргументов. Обратите внимание на вызов ...array_values()? – mariano.iglesias 30 September 2015 в 13:16
  • 4
    @ mariano.iglesias array_values() также доступен в php 4. Не уверен, что это то, что вы подразумеваете под argument unpacking. – ZurabWeb 30 September 2015 в 21:14
  • 5
    @Piero, распаковка Argument - это функция, представленная в PHP 5.6. Это способ предоставить несколько аргументов в качестве массива. Проверьте здесь - php.net/manual/en/… – Anis 21 August 2017 в 05:41

Большинство решений, приведенных здесь для создания подготовленного запроса, являются более сложными, чем они должны быть. Используя встроенные функции PHP, вы можете легко просмотреть инструкцию SQL без значительных накладных расходов.

Учитывая $records, массив записей, где каждая запись сама является индексированным массивом (в форме field => value), следующая функция будет вставлять записи в указанную таблицу $table, on соединение PDO $connection, используя только один подготовленный оператор. Обратите внимание, что это решение PHP 5.6+ из-за использования распаковки аргументов в вызове array_push:

private function import(PDO $connection, $table, array $records)
{
    $fields = array_keys($records[0]);
    $placeHolders = substr(str_repeat(',?', count($fields)), 1);
    $values = [];
    foreach ($records as $record) {
        array_push($values, ...array_values($record));
    }

    $query = 'INSERT INTO ' . $table . ' (';
    $query .= implode(',', $fields);
    $query .= ') VALUES (';
    $query .= implode('),(', array_fill(0, count($records), $placeHolders));
    $query .= ')';

    $statement = $connection->prepare($query);
    $statement->execute($values);
}
-1
ответ дан mariano.iglesias 22 August 2018 в 13:06
поделиться
  • 1
    Этот код никогда не должен использоваться, поскольку он уязвим для SQL-инъекций – Your Common Sense 30 September 2015 в 13:23

Пример моего реального мира для вставки всех немецких почтовых индексов в пустую таблицу (чтобы добавить названия городов позже):

// obtain column template
$stmt = $db->prepare('SHOW COLUMNS FROM towns');
$stmt->execute();
$columns = array_fill_keys(array_values($stmt->fetchAll(PDO::FETCH_COLUMN)), null);
// multiple INSERT
$postcode = '01000';// smallest german postcode
while ($postcode <= 99999) {// highest german postcode
    $values = array();
    while ($postcode <= 99999) {
        // reset row
        $row = $columns;
        // now fill our row with data
        $row['postcode'] = sprintf('%05d', $postcode);
        // build INSERT array
        foreach ($row as $value) {
            $values[] = $value;
        }
        $postcode++;
        // avoid memory kill
        if (!($postcode % 10000)) {
            break;
        }
    }
    // build query
    $count_columns = count($columns);
    $placeholder = ',(' . substr(str_repeat(',?', $count_columns), 1) . ')';//,(?,?,?)
    $placeholder_group = substr(str_repeat($placeholder, count($values) / $count_columns), 1);//(?,?,?),(?,?,?)...
    $into_columns = implode(',', array_keys($columns));//col1,col2,col3
    // this part is optional:
    $on_duplicate = array();
    foreach ($columns as $column => $row) {
        $on_duplicate[] = $column;
        $on_duplicate[] = $column;
    }
    $on_duplicate = ' ON DUPLICATE KEY UPDATE' . vsprintf(substr(str_repeat(', %s = VALUES(%s)', $count_columns), 1), $on_duplicate);
    // execute query
    $stmt = $db->prepare('INSERT INTO towns (' . $into_columns . ') VALUES' . $placeholder_group . $on_duplicate);//INSERT INTO towns (col1,col2,col3) VALUES(?,?,?),(?,?,?)... {ON DUPLICATE...}
    $stmt->execute($values);
}

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

    $row['postcode'] = sprintf('%05d', $postcode);

Я горжусь некоторыми конструкторами строкой запроса, поскольку они работают без тяжелых массивных функций, таких как array_merge. Особенно vsprintf () была хорошей находкой.

Наконец, мне нужно было добавить 2x while (), чтобы избежать превышения предела памяти. Это зависит от вашего предела памяти, но при этом его общее общее решение для предотвращения проблем (и 10 запросов по-прежнему намного лучше 10.000).

0
ответ дан mgutt 22 August 2018 в 13:06
поделиться

Вот класс, который я написал, делает несколько вложений с опцией очистки:

<?php

/**
 * $pdo->beginTransaction();
 * $pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
 * $pmi->insertRow($data);
 * ....
 * $pmi->insertRow($data);
 * $pmi->purgeRemainingInserts();
 * $pdo->commit();
 *
 */
class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    function __construct(\PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "INSERT INTO `$tableName` (`".implode("`,`", $fieldsAsArray)."`) VALUES";
        $questionMarks  = " (?".str_repeat(",?", $this->_numberOfFields - 1).")";

        $this->_purgeAtCount = $bigInsertCount;
        $this->_bigInsertQuery    = $pdo->prepare($insertIntoPortion.$questionMarks.str_repeat(", ".$questionMarks, $bigInsertCount - 1));
        $this->_singleInsertQuery = $pdo->prepare($insertIntoPortion.$questionMarks);
    }

    function insertRow($rowData) {
        // @todo Compare speed
        // $this->_currentlyInsertingRows = array_merge($this->_currentlyInsertingRows, $rowData);
        foreach($rowData as $v) array_push($this->_currentlyInsertingRows, $v);
        //
        if (++$this->_currentlyInsertingCount == $this->_purgeAtCount) {
            if ($this->_bigInsertQuery->execute($this->_currentlyInsertingRows) === FALSE) {
                $this->_error = "Failed to perform a multi-insert (after {$this->_insertCount} inserts), the following errors occurred:".implode('<br/>', $this->_bigInsertQuery->errorInfo());
                return false;
            }
            $this->_insertCount++;

            $this->_currentlyInsertingCount = 0;
            $this->_currentlyInsertingRows = array();
        }
        return true;
    }

    function purgeRemainingInserts() {
        while ($this->_currentlyInsertingCount > 0) {
            $singleInsertData = array();
            // @todo Compare speed - http://www.evardsson.com/blog/2010/02/05/comparing-php-array_shift-to-array_pop/
            // for ($i = 0; $i < $this->_numberOfFields; $i++) $singleInsertData[] = array_pop($this->_currentlyInsertingRows); array_reverse($singleInsertData);
            for ($i = 0; $i < $this->_numberOfFields; $i++) array_unshift($singleInsertData, array_pop($this->_currentlyInsertingRows));

            if ($this->_singleInsertQuery->execute($singleInsertData) === FALSE) {
                $this->_error = "Failed to perform a small-insert (whilst purging the remaining rows; the following errors occurred:".implode('<br/>', $this->_singleInsertQuery->errorInfo());
                return false;
            }
            $this->_currentlyInsertingCount--;
        }
    }

    public function getError() {
        return $this->_error;
    }
}
3
ответ дан Pierre Dumuid 22 August 2018 в 13:06
поделиться
  • 1
    Привет Пьер. Может быть, вы больше не активны здесь. Тем не менее, я просто хотел указать, что моя идея по этой проблеме выглядит почти идентичной вашей. Чистое совпадение, поскольку я думаю, что этого не так много. Я добавил классы для DELETE- и UPDATE-Operations, а также добавил некоторые идеи отсюда. Я просто не видел твой класс. Пожалуйста, извините мое бесстыдное самооценку здесь, но я думаю, это поможет кому-то. Надеюсь, это не против SO-правил. Найти здесь . – user3469861 2 August 2017 в 14:55

Объединение массива должно быть еще быстрее, чем array_push, поэтому что-то вроде:

$cumulativeArray += $rowArray; 
-1
ответ дан S.L. Barth 22 August 2018 в 13:06
поделиться

Вот мое решение: https://github.com/sasha-ch/Aura.Sql на основе библиотеки auraphp / Aura.Sql.

Пример использования:

$q = "insert into t2(id,name) values (?,?), ... on duplicate key update name=name"; 
$bind_values = [ [[1,'str1'],[2,'str2']] ];
$pdo->perform($q, $bind_values);

Бугеры приветствуются.

0
ответ дан sasha-ch 22 August 2018 в 13:06
поделиться

Это просто не так, как вы используете подготовленные заявления.

Вполне нормально вставлять одну строку для каждого запроса, потому что вы можете выполнять один подготовленный оператор несколько раз с разными параметрами. На самом деле это одно из самых больших преимуществ, так как позволяет вам вставлять вам большое количество строк эффективным, безопасным и удобным способом.

. Возможно, возможно реализовать схему, которую вы предлагаете, по крайней мере для фиксированного числа строк, но почти гарантировано, что это не совсем то, что вы хотите.

14
ответ дан sebasgo 22 August 2018 в 13:06
поделиться
  • 1
    Можете ли вы предложить лучший способ вставить несколько строк в таблицу? – Crashthatch 4 July 2014 в 16:51
  • 2
    @Crashthatch: просто сделайте это наивным способом: установите подготовленный оператор один раз, затем выполните его для каждой строки с разными значениями для связанных параметров. Это второй подход в ответе Зыка. – sebasgo 7 July 2014 в 08:53
  • 3
    Цель, которую вы упомянули для подготовленного заявления, верна. Но использование multi -insert - еще один способ улучшить скорость вставки, и его можно использовать с подготовленным заявлением. По моему опыту, при миграции 30 миллионов строк данных с помощью подготовленного отчета PDO, я видел, что multi-insert была в 7-10 раз быстрее, чем сгруппирована одна вставка в транзакции. – Anis 21 August 2017 в 05:50
  • 4
    Абсолютно согласен с Анисом. У меня есть 100k строк и получить огромное увеличение скорости с вставками строк muli. – Kenneth 11 March 2018 в 10:09

Тот же ответ, что и г-н Балагтас, немного яснее ...

Последние версии MySQL и PHP PDO поддерживают многострочные INSERT операторы.

Обзор SQL

SQL будет выглядеть примерно так: предположим, что таблица с тремя столбцами вы хотите INSERT.

INSERT INTO tbl_name
            (colA, colB, colC)
     VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) [,...]

ON DUPLICATE KEY UPDATE работает так, как ожидалось, даже с несколькими строками ВСТАВИТЬ; добавьте это:

ON DUPLICATE KEY UPDATE colA = VALUES(colA), colB = VALUES(colB), colC = VALUES(colC)

Обзор PHP

Ваш PHP-код будет следовать обычным вызовам PDO $pdo->prepare($qry) и $stmt->execute($params).

$params будет быть 1-мерным массивом всех значений, которые нужно передать в INSERT.

В приведенном выше примере он должен содержать 9 элементов; PDO будет использовать каждый набор из 3 как одну строку значений. [Вставка 3 строк по 3 столбца каждый = 9 элементов массива.)

Реализация

Ниже код написан для ясности, а не эффективности. Работайте с функциями PHP array_*() для лучшего способа отображения или просмотра ваших данных, если хотите.

Предполагая:

  • $tblName - имя строки таблицы INSERT для
  • $colNames - 1-мерный массив имен столбцов таблицы. Эти имена столбцов должны быть действительными идентификаторами столбцов MySQL; избегать их с обратными шагами (``), если они не
  • $dataVals - mutli-мерный массив, где каждый элемент представляет собой 1-й массив строки значений INSERT

Пример кода

// setup data values for PDO
// memory warning: this is creating a copy all of $dataVals
$dataToInsert = array();

foreach ($dataVals as $row => $data) {
    foreach($data as $val) {
        $dataToInsert[] = $val;
    }
}

// (optional) setup the ON DUPLICATE column names
$updateCols = array();

foreach ($colNames as $curCol) {
    $updateCols[] = $curCol . " = VALUES($curCol)";
}

$onDup = implode(', ', $updateCols);

// setup the placeholders - a fancy way to make the long "(?, ?, ?)..." string
$rowPlaces = '(' . implode(', ', array_fill(0, count($colNames), '?')) . ')';
$allPlaces = implode(', ', array_fill(0, count($dataVals), $rowPlaces));

$sql = "INSERT INTO $tblName (" . implode(', ', $colNames) . 
    ") VALUES " . $allPlaces . " ON DUPLICATE KEY UPDATE $onDup";

// and then the PHP PDO boilerplate
$stmt = $pdo->prepare ($sql);

try {
   $stmt->execute($dataToInsert);
} catch (PDOException $e){
   echo $e->getMessage();
}

$pdo->commit();
61
ответ дан shinypenguin 22 August 2018 в 13:06
поделиться
  • 1
    Это очень плохо, что PDO обрабатывает его таким образом, есть несколько очень элегантных способов сделать это в других драйверах DB. – Jonathon 6 May 2013 в 03:00
  • 2
    Это еще больше упрощает компоновщики, делая $rowPlaces более не необходимым: $allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')')); – Phil 19 June 2014 в 05:03
  • 3
    Прекрасно работает. Я бы добавил к этому ответу необходимость обеспечения уникальности (комбинации) индексов в таблице. Как в ALTER TABLE votes ADD UNIQUE unique_index (user, email, address); – Giuseppe 2 April 2017 в 15:22
  • 4
    Потрясающие! BTW, используя array_push($dataToInsert, ...array_values($dataVals));, будет намного быстрее, чем foreach ($dataVals as $row => $data) {} – Anis 21 August 2017 в 09:29

test.php

<?php
require_once('Database.php');

$obj = new Database();
$table = "test";

$rows = array(
    array(
    'name' => 'balasubramani',
    'status' => 1
    ),
    array(
    'name' => 'balakumar',
    'status' => 1
    ),
    array(
    'name' => 'mani',
    'status' => 1
    )
);

var_dump($obj->insertMultiple($table,$rows));
?>

Database.php

<?php
class Database 
{

    /* Initializing Database Information */

    var $host = 'localhost';
    var $user = 'root';
    var $pass = '';
    var $database = "database";
    var $dbh;

    /* Connecting Datbase */

    public function __construct(){
        try {
            $this->dbh = new PDO('mysql:host='.$this->host.';dbname='.$this->database.'', $this->user, $this->pass);
            //print "Connected Successfully";
        } 
        catch (PDOException $e) {
            print "Error!: " . $e->getMessage() . "<br/>";
            die();
        }
    }
/* Insert Multiple Rows in a table */

    public function insertMultiple($table,$rows){

        $this->dbh->beginTransaction(); // also helps speed up your inserts.
        $insert_values = array();
        foreach($rows as $d){
            $question_marks[] = '('  . $this->placeholders('?', sizeof($d)) . ')';
            $insert_values = array_merge($insert_values, array_values($d));
            $datafields = array_keys($d);
        }

        $sql = "INSERT INTO $table (" . implode(",", $datafields ) . ") VALUES " . implode(',', $question_marks);

        $stmt = $this->dbh->prepare ($sql);
        try {
            $stmt->execute($insert_values);
        } catch (PDOException $e){
            echo $e->getMessage();
        }
        return $this->dbh->commit();
    }

    /*  placeholders for prepared statements like (?,?,?)  */

    function placeholders($text, $count=0, $separator=","){
        $result = array();
        if($count > 0){
            for($x=0; $x<$count; $x++){
                $result[] = $text;
            }
        }

        return implode($separator, $result);
    }

}
?>
0
ответ дан sonofkrish 22 August 2018 в 13:06
поделиться
  • 1
    Добро пожаловать в stackoverflow. Не только код, пожалуйста, напишите о своей проблеме и объясните. – Prakash Palnati 20 November 2017 в 10:23
  • 2
    в основном. это всего лишь реализация кода, представленного в принятом ответе – Your Common Sense 21 November 2017 в 10:26

Вот как я это сделал:

Сначала определите имена колонок, которые вы будете использовать, или оставьте поле пустым, и pdo предположит, что вы хотите использовать все столбцы таблицы - в этом случае вы «Мне нужно сообщить значения строк в том порядке, в каком они появляются в таблице.

$cols = 'name', 'middleName', 'eMail';
$table = 'people';

Теперь предположим, что у вас уже подготовлен двухмерный массив. Итерации и построение строки с вашими значениями строк:

foreach ( $people as $person ) {
if(! $rowVals ) {
$rows = '(' . "'$name'" . ',' . "'$middleName'" . ',' .           "'$eMail'" . ')';
} else { $rowVals  = '(' . "'$name'" . ',' . "'$middleName'" . ',' . "'$eMail'" . ')';
}

Теперь, что вы только что сделали, это проверить, были ли уже определены строки $, а если нет, создайте их и сохраните значения строк и необходимый синтаксис SQL, поэтому он будет действительным оператором. Обратите внимание, что строки должны входить в двойные кавычки и одинарные кавычки, поэтому они будут незамедлительно распознаны как таковые.

Все, что осталось сделать, это подготовить оператор и выполнить как таковой:

$stmt = $db->prepare ( "INSERT INTO $table $cols VALUES $rowVals" );
$stmt->execute ();

Протестировано до 2000 строк до сих пор, а время исполнения удручающее. Запустит еще несколько тестов и вернется сюда, если у меня есть что-то еще.

С уважением.

1
ответ дан Théo T. Carranza 22 August 2018 в 13:06
поделиться

Вот мой простой подход.

    $values = array();
    foreach($workouts_id as $value){
      $_value = "(".$value.",".$plan_id.")";
      array_push($values,$_value);
    }
    $values_ = implode(",",$values);

    $sql = "INSERT INTO plan_days(id,name) VALUES" . $values_."";
    $stmt = $this->conn->prepare($sql);
    $stmt->execute();
3
ответ дан user 22 August 2018 в 13:06
поделиться
  • 1
    Работая для меня, спасибо! – Do Xuan Nguyen 11 May 2017 в 07:15
  • 2
    вы нарушаете точку использования подготовленных инструкций. op обеспокоен проблемой безопасности в вопросе On the readings on PDO, the use prepared statements should give me a better security than static queries. – yesitsme 7 September 2017 в 06:08
  • 3
    Просто визуализируйте, что у вас есть не проверенный $workouts_id, который может иметь $value с довольно неожиданными данными. Вы не можете гарантировать, что, возможно, не сейчас, но в будущем другой разработчик сделает эти данные небезопасными. Поэтому я думаю, что более правильно сделать запрос подготовленным PDO. – Nikita_kharkov_ua 31 May 2018 в 20:54

Два возможных подхода:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:v1_1, :v1_2, :v1_3),
    (:v2_1, :v2_2, :v2_3),
    (:v2_1, :v2_2, :v2_3)');
$stmt->bindValue(':v1_1', $data[0][0]);
$stmt->bindValue(':v1_2', $data[0][1]);
$stmt->bindValue(':v1_3', $data[0][2]);
// etc...
$stmt->execute();

Или:

$stmt = $pdo->prepare('INSERT INTO foo VALUES(:a, :b, :c)');
foreach($data as $item)
{
    $stmt->bindValue(':a', $item[0]);
    $stmt->bindValue(':b', $item[1]);
    $stmt->bindValue(':c', $item[2]);
    $stmt->execute();
}

Если данные для всех строк находятся в одном массиве, я бы использовал второе решение.

13
ответ дан Zyx 22 August 2018 в 13:06
поделиться
  • 1
    в последнем вы не делаете несколько (возможно, тысяч) отдельных вызовов исполнения вместо объединения в одно утверждение? – JM4 31 January 2012 в 22:24
  • 2
    @ JM4, вы предлагаете $stmt->execute(); быть вне цикла foreach? – bafromca 8 August 2013 в 17:13
  • 3
    @bafromca - Да, я. См. Мой ответ выше с upvotes. В чистом вставке есть аргумент no , я могу логически придумать, что это не может быть один оператор. Один вызов, один выполняется. На самом деле, мой ответ с начала 2012 года можно было бы улучшить еще больше - что-то я буду делать позже, когда у меня будет больше времени. Если вы начнете бросать комбинации Insert / update / delete, это совсем другая история. – JM4 8 August 2013 в 21:13
0
ответ дан Dardan 5 November 2018 в 10:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: