PDO Hazırlık Tek bir sorguya birden çok satır ekler


146

Şu anda tek bir sorguda birden çok değer satırı eklemek için MySQL SQL bu tür kullanıyorum:

INSERT INTO `tbl` (`key1`,`key2`) VALUES ('r1v1','r1v2'),('r2v1','r2v2'),...

PDO okumalarında, hazırlanan ifadelerin kullanımı bana statik sorgulardan daha iyi bir güvenlik sağlamalıdır.

Bu nedenle hazırlanan ifadeleri kullanarak "bir sorgu kullanarak birden çok değer satırı ekleme" oluşturmak mümkün olup olmadığını bilmek istiyorum.

Evet ise, bunu nasıl uygulayabilirim?


$stmt->execute($data); php.net/manual/en/… için bir çok cevap ile dikkatli olun Temelde tüm parametreler dizeler olarak onaylanır. Sorguyu oluşturduktan sonra verileri manuel olarak döngüye sokun ve üçüncü bağımsız değişken olarak el ile bindValueveya bindParamgeçirerek yazın.
MrMesees

Yanıtlar:


151

PDO Hazırlanan Deyimlerle Birden Çok Değer Ekleme

Bir execute deyimine birden çok değer ekleme. Neden bu sayfaya göre normal uçlardan daha hızlı.

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

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

daha fazla veri değeri veya muhtemelen verileri dolduran bir döngünüz var.

Hazırlanmış ekler ile, eklediğiniz alanları ve? Oluşturulacak alan sayısını bilmeniz gerekir. parametrelerinizi bağlamak için yer tutucular.

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

Temelde insert deyiminin böyle görünmesini istiyoruz.

Şimdi, kod:

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();

Her ne kadar testimde, birden fazla kesici uç ve tek değerli düzenli hazırlanmış kesici uçlar kullanılırken sadece 1 sn fark vardı.


4
Yukarıdaki açıklamada bir yazım hatası, $ datafield $ sql'da kullanılmasına rağmen $ datafields değerinden bahsetmektedir. Bu nedenle kopyala yapıştırma işlemi hataya neden olur. Lütfen düzeltin. Bu çözüm için teşekkürler.
pal4life

1
Bunu bir süre kullandıktan sonra, içinde tek tırnak bulunan değerlerin düzgün kaçmadığını fark ettim. Patlamada çift tırnak kullanmak benim için cazibe gibi çalışır: $ a [] = '("'. İmplode (", ", $ question_marks). '", NOW ())';
qwertzman

1
array_merge, bir array_push kullanmaktan daha pahalı görünüyor.
K2xL

14
"Yalnızca 1 sn fark vardı" dediğinizde, kaç satır veri ekliyorsunuz? 1 sn, bağlama göre oldukça önemlidir.
Kevin Dice

3
Optimizasyon: placeholders()Tekrar tekrar aramanın anlamı yok . Döngüden önce bir kez çağırın sizeof($datafields)ve sonuç dizesini $question_marks[]döngünün içine ekleyin .
AVIDeveloper

71

Bay Balagtas ile aynı cevap, biraz daha net ...

Son sürümler MySQL ve PHP PDO do destek çok sıralı INSERTifadeleri.

SQL Genel Bakış

İstediğiniz 3 sütunlu bir tablo varsayarak SQL böyle bir şeye benzeyecektir INSERT.

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

ON DUPLICATE KEY UPDATEçok sıralı bir INSERT ile bile beklendiği gibi çalışır; bunu ekleyin:

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

PHP'ye Genel Bakış

PHP kodunuz normal $pdo->prepare($qry)ve $stmt->execute($params)PDO çağrılarını takip eder .

$params, iletilecek tüm değerlerin 1 boyutlu bir dizisi olacaktır INSERT.

Yukarıdaki örnekte, 9 öğe içermelidir; PDO, her 3 kümesini tek bir değer satırı olarak kullanacaktır. (Her biri 3 sütunlu 3 satır ekleme = 9 eleman dizisi.)

uygulama

Verimlilik için değil, açıklık için aşağıdaki kod yazılmıştır. array_*()İsterseniz verilerinizi haritalamak veya üzerinde gezinmek için PHP işlevleriyle birlikte çalışın . İşlemleri kullanıp kullanamayacağınız açıkça MySQL tablo türünüze bağlıdır.

varsayarsak:

  • $tblName - eklenecek tablonun dize adı
  • $colNames- Tablonun sütun adlarının 1 boyutlu dizisi Bu sütun adları geçerli MySQL sütun tanımlayıcıları olmalıdır; Olmazsa onlarla backticks (``) kaçmak
  • $dataVals - her bir öğenin INSERT değerine bir 1-d dizisi olduğu çoklu boyutlu dizi

Basit kod

// 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();

6
PDO'nun bu şekilde ele alması gerçekten çok kötü, bunu diğer DB sürücülerinde yapmanın çok zarif yolları var.
Jonathon

Bu, yer tutucuları daha da tersine çevirerek $rowPlacesartık gerekli değildir:$allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')'));
Phil

Mükemmel çalışıyor. Bu cevaba tablodaki (kombinasyonu) indekslerinin benzersizliğini sağlama ihtiyacını da ekleyeceğim. Alter TABLO gibi votesADD benzersiz unique_index( user, email, address);
Giuseppe

1
Müthiş! BTW, kullanmak array_push($dataToInsert, ...array_values($dataVals));o zaman çok daha hızlı olacakforeach ($dataVals as $row => $data) {}
Anis

39

Ne için değer, ben bir çok kullanıcı seçilen cevap yaptığı gibi tek bir dize sorgu olarak bina yerine INSERT ifadeleri üzerinden yineleme tavsiye gördük. Sadece iki alan ve çok basit bir insert deyimi ile basit bir test yapmaya karar verdim:

<?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>";
?>

Genel sorgunun kendisi milisaniye veya daha az sürerken, ikinci (tek dize) sorgusu sürekli olarak 8 kat veya daha hızlıydı. Bu, binlerce sütunun daha fazla sütuna aktarılmasını yansıtmak için oluşturulmuşsa, fark çok büyük olabilir.


@ JM4 - 10 satırı doğrudan bir yürütmeye koymak için harika bir fikir . Ancak, JSON gibi bir nesnede saklandıklarında binlerce satırı nasıl ekleyebilirim? Aşağıdaki kodum perferctly çalışıyor. Ancak bir yürütmede 10 satır eklemek için nasıl ayarlayabilirim? `foreach ($ json_content $ datarow olarak) {$ id = $ datarow [id]; $ date = $ datarow [tarih]; $ row3 = $ datarow [satır3]; $ row4 = $ datarow [satır4]; $ row5 = $ datarow [satır5]; $ row6 = $ datarow [satır6]; $ row7 = $ datarow [satır7]; // şimdi execute $ databaseinsert-> execute (); } // foreach'in sonu `
Peter

@ JM4 - ... ve ikinci sorum şu: "neden bind_paramikinci ithalat rutinde bir ifade yok"?
Peter

İki kere ilmek yapmanıza gerek yok mu? Ayrıca dinamik üretmek zorunda kalacak (?,?), doğru?
NoobishPro

@NoobishPro Evet, her ikisini birden oluşturmak için / foreach için aynısını kullanabilirsiniz.
Chazy Chaz

34

$ Veri dizisi küçük olduğunda Herbert Balagtas'ın Kabul Edilen Cevabı iyi çalışır. Daha büyük $ veri dizileri ile array_merge işlevi belirgin biçimde yavaşlar. $ Veri dizisi oluşturmak için test dosyam 28 sütun ve yaklaşık 80.000 satır var. Son betiğin tamamlanması 41 saniye sürdü .

Array_merge () yerine $ insert_values ​​oluşturmak için array_push () kullanılması , 0.41s yürütme süresi ile 100X hızlanmaya neden oldu .

Sorunlu 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 () gereksinimini ortadan kaldırmak için, aşağıdaki iki diziyi oluşturabilirsiniz:

//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 ); 

Bu diziler daha sonra aşağıdaki gibi kullanılabilir:

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();

4
PHP 5.6'da array_push($data, ...array_values($row))bunun yerine yapabilirsiniz $data = array_merge($data, array_values($row));. Çok daha hızlı.
mpen

Neden 5.6? Belgeler 5.6 hakkında hiçbir şey söylemiyor array_push(), php 4'te bile mevcut.
ZurabWeb

1
@Piero PHP 5.6+ sadece kod kullanımı array_push()nedeniyle değil, @Mark argüman paketi açma kullanıyor çünkü. Uyarı ...array_values()orada çağrıyı?
mariano.iglesias

@ mariano.iglesias array_values()php 4'te de mevcut. Ne demek istediğinizden emin değil misiniz argument unpacking?
ZurabWeb

2
@Piero, Argüman açma PHP 5.6'da sunulan bir özelliktir. Dizi olarak birden çok argüman sağlamanın bir yoludur. Buradan kontrol edin - php.net/manual/tr/…
Anis

14

İki olası yaklaşım:

$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();

Veya:

$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();
}

Tüm satırların verileri tek bir dizide ise, ikinci çözümü kullanırdım.


10
ikincisinde, tek bir ifadede birleştirmek yerine birkaç (muhtemelen binlerce) ayrı yürütme çağrısı yapmıyor musunuz?
JM4

@ JM4, $stmt->execute();foreach döngüsünün dışında olmasını mı öneriyorsunuz ?
bafromca

@bafromca - Evet, benim. Cevaplarımı yukarıda yukarıdaki cevap bakın. Saf insert deyiminde , mantıksal olarak tek bir ifade olamayacağına dair hiçbir neden yoktur . Bir çağrı, bir yürütme. Aslında, 2012'nin başlarındaki cevabım daha da geliştirilebilir - biraz daha zamanım olduğunda daha sonra yapacağım. Kombinasyon ekle / güncelle / sil'e atmaya başlarsanız, bu farklı bir hikaye.
JM4

12

Hazırlanan ifadeleri kullanma şekliniz bu değildir.

Hazırlanan bir ifadeyi farklı parametrelerle birden çok kez yürütebileceğiniz için sorgu başına bir satır eklemek mükemmeldir. Aslında bu, size çok sayıda satırı verimli, güvenli ve rahat bir şekilde eklemenize izin verdiği için en büyük avantajlardan biridir.

Bu nedenle, en azından sabit sayıda satır için önerdiğiniz şemayı uygulamak mümkün olabilir, ancak bunun gerçekten istediğiniz şey olmadığı neredeyse garanti edilir.


1
Bir tabloya birden çok satır eklemenin daha iyi bir yolunu önerebilir misiniz?
Crashthatch

@Crashthatch: Sadece saf bir şekilde yapın: Hazırlanan ifadeyi bir kez ayarlayın, ardından her satır için ilişkili parametreler için farklı değerlerle yürütün. Bu Zyk'in cevabındaki ikinci yaklaşım.
sebasgo

2
Hazırlanan ifade için bahsettiğiniz amaç doğrudur. Ancak, çoklu-inert kullanmak kesici uç hızını arttırmak için başka bir tekniktir ve hazırlanmış deyimle de kullanılabilir. Deneyimlerime göre, PDO tarafından hazırlanan ifadeyi kullanarak 30 milyon satırlık veri taşırken, çoklu insertin işlemlerde tekli insertin 7-10 kat daha hızlı olduğunu gördüm.
Anis

1
Anis'e kesinlikle katılıyorum. 100k sıralarım var ve muli sıralı uçlarla büyük bir hız artışı elde ediyorum.
Kenneth

İlişkisel veritabanını satır başına bir döngüde çağırmanın genellikle iyi bir şey olduğunu iddia etmek, kabul edemediğim bir şeydir. Bunun için aşağı oy. Verilen, bazen sorun değil. Ben mühendislik ile mutlak inanmıyorum. Ancak bu, yalnızca belirli durumlarda kullanılması gereken bir anti-modeldir.
Brandon

8

Daha kısa bir cevap: sütunlar tarafından sıralanan veri dizisini düzleştirin

//$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";

1.000 kadar kayıt eklerken, ihtiyacınız olan tek şey değerlerin bir sayımı olduğunda, her kayıt arasında döngü yapmak zorunda kalmazsınız.


5

İşte benim basit yaklaşımım.

    $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();

6
hazırlanmış ifadeleri kullanma noktasını yeniyorsunuz. op, On the readings on PDO, the use prepared statements should give me a better security than static queries.
sorudaki

2
Sadece doğrulanmamış veriler ile görüntüleme, oldukça ucuz verilerle s $workouts_idolabilir $value. Belki şimdi değil ama gelecekte başka bir geliştiricinin bu verileri güvensiz hale getireceğini garanti edemezsiniz. Bu yüzden PDO tarafından hazırlanan sorguyu daha doğru yapmak düşünüyorum.
Nikita_kharkov_ua

3

İşte yazdığım bir sınıf, tasfiye seçeneği ile birden fazla kesici uç yapmak:

<?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;
    }
}

Merhaba Pierre. Belki artık burada aktif değilsin. Yine de, sadece bu konudaki fikrimin seninkine neredeyse benzediğini belirtmek istedim. Saf tesadüf, tahminimce bundan daha fazlası yok. DELETE- VE UPDATE-Operations için de sınıflar ekledim ve daha sonra buradan bazı fikirler ekledim. Sadece sınıfını görmedim. Lütfen buradaki utanmaz öz tanıtımımı özür dilerim, ama sanırım birisi için yardımcı olacak. Umarım bu SO kurallarına aykırı değildir. Burada bulun .
JackLeEmmerdeur

1

Ben böyle yaptım:

Önce kullanacağınız sütun adlarını tanımlayın veya boş bırakın; pdo, tablodaki tüm sütunları kullanmak istediğinizi varsayar; bu durumda satır değerlerini tabloda göründükleri sırayla bilgilendirmeniz gerekir. .

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

Şimdi, önceden hazırlanmış iki boyutlu bir diziniz olduğunu varsayalım. Yineleyin ve satır değerlerinizle bir dize oluşturun, örneğin:

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

Şimdi, yaptığınız şey $ satırlarının önceden tanımlanmış olup olmadığını kontrol etmekti ve eğer değilse, onu oluşturun ve satır değerlerini ve gerekli SQL sözdizimini saklayın, böylece geçerli bir ifade olacaktır. Dizelerin çift tırnak ve tek tırnak içine alınması gerektiğini unutmayın, böylece bu şekilde derhal tanınırlar.

Yapılması gereken tek şey ifadeyi hazırlamak ve yürütmektir:

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

Şimdiye kadar 2000 satıra kadar test edildi ve yürütme süresi kasvetli. Daha fazla test yapacağım ve katkıda bulunacak başka bir şeyim olursa buraya geri döneceğim.

Saygılarımızla.


1

Henüz önerilmediğinden, VERİ YÜKLEME YÜKLEME dizin oluşturmayı devre dışı bıraktığından, tüm verileri eklediğinden ve ardından dizinleri yeniden etkinleştirdiğinden emin olmak için veri yüklemenin en hızlı yolu olduğundan eminim.

Verileri bir csv olarak kaydetmek, fputcsv'yi akılda tutarak oldukça önemsiz olmalıdır. MyISAM en hızlıdır, ancak InnoDB'de hala büyük performans elde edersiniz. Diğer dezavantajlar da var, bu yüzden çok fazla veri ekliyorsanız ve 100 satırın altında rahatsız etmiyorsanız bu rotaya giderim.


1

Eski bir soru olmasına rağmen tüm katkılar bana çok yardımcı oldu, bu yüzden kendi DbContextsınıfımda çalışan çözümüm . $rowsParametre sadece satır ya da modeller temsil ilişkisel diziler içeren bir dizidir: field name => insert value.

Model kullanan bir model kullanırsanız, bu, model verileri bir dizi olarak geçirildiğinde güzel bir şekilde uyuyorsa ToRowArray, model sınıfı içindeki bir yöntemden söyleyin .

Not : Bu yönteme iletilen bağımsız değişkenlerin, kullanıcıya doğrulanmış veya onaylanmış ve dezenfekte edilmiş insert değerleri dışında herhangi bir kullanıcı girdisine bağımlı olmasına asla izin vermemelidir. Bağımsız $tableNamedeğişken ve sütun adları çağıran mantık tarafından tanımlanmalıdır; örneğin bir Usermodel, sütun listesi modelin üye alanlarına eşlenmiş olan kullanıcı tablosuna eşlenebilir.

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();
}

tek bir sorgu için bir tane kullanmanın bir anlamı olmadığı için bir işlemden kurtulun. ve her zamanki gibi, bu kod SQL enjeksiyonuna veya sorgu hatasına karşı savunmasızdır.
Ortak Duygunuz

Bu durum için işlemlerin gereksiz kullanımı konusunda haklısınız, ancak bunun SQL enjeksiyonuna karşı nasıl savunmasız olduğunu görmüyorum. Parametrelendirildi, bu yüzden sadece $tableNamekullanıcıya maruz kaldığını varsaydığınızı varsayabilirim , ki bu DAL'da değil. İddialarınızı genişletebilir misiniz? Sadece bir şeyler söylemek yararlı değil.
Lee

Peki, bu sadece bir tablo adı değil, yine de: burada yayınladığınız kodu kullanacak biri
Ortak Duygunuz

Yani kodun veya her bir kaynağın argümanlar için her olası kullanımını özetlemek posterin sorumluluğunda mıdır? Belki insanlardan daha yüksek beklentilerim var. Kullanıcının erişmesine izin vermeyecek bir not eklersem sizi daha mutlu eder $tableNamemi?
Lee

Eğer amacı sadece birine göstermek değil, birine yardım etmekse, güvenilir bir kod göndermek posterin sorumluluğundadır.
Ortak Duygunuz

1

İşte bu sorun için başka bir (ince) çözüm:

İlk önce kaynak dizinin verilerini (burada: $ aData) count () ile saymanız gerekir. Daha sonra array_fill () kullanın ve kaynak dizinin sahip olduğu kadar çok giriş içeren, her biri "(?,?)" Değerine sahip yeni bir dizi oluşturun (yer tutucu sayısı kullandığınız alanlara bağlıdır; burada: 2). Daha sonra üretilen dizinin yerleştirilmesi gerekir ve tutkal olarak bir virgül kullanılır. Foreach döngüsü içinde, kullandığınız yer tutucuların sayısı (yer tutucuların sayısı * geçerli dizi dizini + 1) ile ilgili başka bir dizin oluşturmanız gerekir. Her bağlanan değerden sonra oluşturulan dizine 1 eklemeniz gerekir.

$do = $db->prepare("INSERT INTO table (id, name) VALUES ".implode(',', array_fill(0, count($aData), '(?,?)')));

foreach($aData as $iIndex => $aValues){
 $iRealIndex = 2 * $iIndex + 1;
 $do->bindValue($iRealIndex, $aValues['id'], PDO::PARAM_INT);
 $iRealIndex = $iRealIndex + 1;
 $do->bindValue($iRealIndex, $aValues['name'], PDO::PARAM_STR);
}

$do->execute();

0

Bu işlevle tek bir sorguya birden çok satır ekleyebilirsiniz:

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 bir dizi değer dizisidir . Sizin durumunuzda

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

Bu, tek bir sorgu ile birden çok satır eklerken hazırlanmış ifadeleri kullanmanın avantajına sahiptir . Güvenlik!


0

İşte benim çözümüm: https://github.com/sasha-ch/Aura.Sql auraphp / Aura.Sql kütüphanesine dayalı.

Kullanım örneği:

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

Hata raporları kabul edilir.


2.4'ten itibaren github.com/auraphp/Aura.SqlQuery/tree/… ile çoklu kesici uç oluşturabilir ve yürütmek için ExtendedPdo kullanabilirsiniz :).
Hari KT

0

Tüm Alman posta kodlarını boş bir tabloya eklemek için gerçek dünya örneğim (daha sonra kasaba isimleri eklemek için):

// 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);
}

Gördüğünüz gibi tamamen esnek. Sütun miktarını veya sütununuzun hangi konumda olduğunu kontrol etmenize gerek yoktur. Yalnızca ekleme verilerini ayarlamanız gerekir:

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

Dizi_merge gibi ağır dizi işlevleri olmadan çalıştıkça bazı sorgu dizesi kurucuları gurur duyuyorum. Özellikle vsprintf () iyi bir bulmak oldu.

Sonunda bellek sınırını aşmamak için 2x while () eklemem gerekiyordu. Bu, bellek sınırınıza bağlıdır, ancak sorunlardan kaçınmak için iyi bir genel çözümdür (ve 10 sorguya sahip olmak hala 10.000'den daha iyidir).


0

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));
?>

DatabaseFunctions.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);
    }

}
?>

Stackoverflow'a hoş geldiniz. Sadece kod değil, lütfen sorununuzu gönderin ve açıklayın.
Prakash Palnati

temelde. bu sadece kabul edilen cevapta verilen kodun bir uygulamasıdır
Your Common Sense

0

Ben de aynı sorunu vardı ve bu kendim için nasıl başarmak olduğunu, ve ben kendim için bir işlev yaptı (ve bu size yardımcı olursa kullanabilirsiniz).

Misal:

Ülkelere (ülke, şehir) DEĞERLER (INSERT) (Almanya, Berlin), (Fransa, Paris);

$arr1 = Array("Germany", "Berlin");
$arr2 = Array("France", "France");

insertMultipleData("countries", Array($arr1, $arr2));


// Inserting multiple data to the Database.
public function insertMultipleData($table, $multi_params){
    try{
        $db = $this->connect();

        $beforeParams = "";
        $paramsStr = "";
        $valuesStr = "";

        for ($i=0; $i < count($multi_params); $i++) { 

            foreach ($multi_params[$i] as $j => $value) {                   

                if ($i == 0) {
                    $beforeParams .=  " " . $j . ",";
                }

                $paramsStr .= " :"  . $j . "_" . $i .",";                                       
            }

            $paramsStr = substr_replace($paramsStr, "", -1);
            $valuesStr .=  "(" . $paramsStr . "),"; 
            $paramsStr = "";
        }


        $beforeParams = substr_replace($beforeParams, "", -1);
        $valuesStr = substr_replace($valuesStr, "", -1);


        $sql = "INSERT INTO " . $table . " (" . $beforeParams . ") VALUES " . $valuesStr . ";";

        $stmt = $db->prepare($sql);


        for ($i=0; $i < count($multi_params); $i++) { 
            foreach ($multi_params[$i] as $j => &$value) {
                $stmt->bindParam(":" . $j . "_" . $i, $value);                                      
            }
        }

        $this->close($db);
        $stmt->execute();                       

        return true;

    }catch(PDOException $e){            
        return false;
    }

    return false;
}

// Making connection to the Database 
    public function connect(){
        $host = Constants::DB_HOST;
        $dbname = Constants::DB_NAME;
        $user = Constants::DB_USER;
        $pass = Constants::DB_PASS;

        $mysql_connect_str = 'mysql:host='. $host . ';dbname=' .$dbname;

        $dbConnection = new PDO($mysql_connect_str, $user, $pass);
        $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        return $dbConnection;
    }

    // Closing the connection
    public function close($db){
        $db = null;
    }

Eğer insertMultipleData ($ tablo, $ multi_params) DOĞRU verir , verileriniz sizin veritabanına eklenmiştir.


0

Deneylerime dayanarak, tek işlemde birden çok değer satırına sahip mysql insert ifadesinin en hızlı olduğunu öğrendim.

Ancak, veriler çok fazlaysa, mysql max_allowed_packetayarı tek işlem ekini birden çok değer satırı ile kısıtlayabilir. Bu nedenle, mysql max_allowed_packetboyutundan daha büyük veriler olduğunda aşağıdaki işlevler başarısız olur :

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

Büyük veri ekleme senaryosunda en başarılı olanı transactionSpeedyöntemdir, ancak yukarıda belirtilen yöntemleri daha fazla tüketir. Bu nedenle, bu sorunu çözmek için verilerinizi daha küçük parçalara bölebilir ve tek işlem ekleme işlemini birden çok kez çağırabilir veya transactionSpeedyöntemi kullanarak yürütme hızından vazgeçebilirsiniz .

İşte araştırmam

<?php

class SpeedTestClass
{
    private $data;

    private $pdo;

    public function __construct()
    {
        $this->data = [];
        $this->pdo = new \PDO('mysql:dbname=test_data', 'admin', 'admin');
        if (!$this->pdo) {
            die('Failed to connect to database');
        }
    }

    public function createData()
    {
        $prefix = 'test';
        $postfix = 'unicourt.com';
        $salutations = ['Mr.', 'Ms.', 'Dr.', 'Mrs.'];

        $csv[] = ['Salutation', 'First Name', 'Last Name', 'Email Address'];
        for ($i = 0; $i < 100000; ++$i) {
            $csv[] = [
                $salutations[$i % \count($salutations)],
                $prefix.$i,
                $prefix.$i,
                $prefix.$i.'@'.$postfix,
            ];
        }

        $this->data = $csv;
    }

    public function truncateTable()
    {
        $this->pdo->query('TRUNCATE TABLE `name`');
    }

    public function transactionSpeed()
    {
        $timer1 = microtime(true);
        $this->pdo->beginTransaction();
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
        $sth = $this->pdo->prepare($sql);

        foreach (\array_slice($this->data, 1) as $values) {
            $sth->execute([
                ':first_name' => $values[1],
                ':last_name' => $values[2],
            ]);
        }

        // $timer2 = microtime(true);
        // echo 'Prepare Time: '.($timer2 - $timer1).PHP_EOL;
        // $timer3 = microtime(true);

        if (!$this->pdo->commit()) {
            echo "Commit failed\n";
        }
        $timer4 = microtime(true);
        // echo 'Commit Time: '.($timer4 - $timer3).PHP_EOL;

        return $timer4 - $timer1;
    }

    public function autoCommitSpeed()
    {
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES (:first_name, :last_name)';
        $sth = $this->pdo->prepare($sql);
        foreach (\array_slice($this->data, 1) as $values) {
            $sth->execute([
                ':first_name' => $values[1],
                ':last_name' => $values[2],
            ]);
        }
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function noBindAutoCommitSpeed()
    {
        $timer1 = microtime(true);

        foreach (\array_slice($this->data, 1) as $values) {
            $sth = $this->pdo->prepare("INSERT INTO `name` (`first_name`, `last_name`) VALUES ('{$values[1]}', '{$values[2]}')");
            $sth->execute();
        }
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsert()
    {
        $timer1 = microtime(true);
        foreach (\array_slice($this->data, 1) as $values) {
            $arr[] = "('{$values[1]}', '{$values[2]}')";
        }
        $sth = $this->pdo->prepare('INSERT INTO `name` (`first_name`, `last_name`) VALUES '.implode(', ', $arr));
        $sth->execute();
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsertWithPlaceholders()
    {
        $placeholders = [];
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
        foreach (\array_slice($this->data, 1) as $values) {
            $placeholders[] = '(?, ?)';
            $arr[] = $values[1];
            $arr[] = $values[2];
        }
        $sql .= implode(', ', $placeholders);
        $sth = $this->pdo->prepare($sql);
        $sth->execute($arr);
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }

    public function singleTransactionInsertWithRollback()
    {
        $placeholders = [];
        $timer1 = microtime(true);
        $sql = 'INSERT INTO `name` (`first_name`, `last_name`) VALUES ';
        foreach (\array_slice($this->data, 1) as $values) {
            $placeholders[] = '(?, ?)';
            $arr[] = $values[1];
            $arr[] = $values[2];
        }
        $sql .= implode(', ', $placeholders);
        $this->pdo->beginTransaction();
        $sth = $this->pdo->prepare($sql);
        $sth->execute($arr);
        $this->pdo->commit();
        $timer2 = microtime(true);

        return $timer2 - $timer1;
    }
}

$s = new SpeedTestClass();
$s->createData();
$s->truncateTable();
echo "Time Spent for singleTransactionInsertWithRollback: {$s->singleTransactionInsertWithRollback()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert: {$s->singleTransactionInsert()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for single Transaction Insert With Placeholders: {$s->singleTransactionInsertWithPlaceholders()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for transaction: {$s->transactionSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for AutoCommit: {$s->noBindAutoCommitSpeed()}".PHP_EOL;
$s->truncateTable();
echo "Time Spent for autocommit with bind: {$s->autoCommitSpeed()}".PHP_EOL;
$s->truncateTable();

Yalnızca iki sütun içeren bir tablo için 100.000 girişin sonuçları aşağıdaki gibidir

$ php data.php
Time Spent for singleTransactionInsertWithRollback: 0.75147604942322
Time Spent for single Transaction Insert: 0.67445182800293
Time Spent for single Transaction Insert With Placeholders: 0.71131205558777
Time Spent for transaction: 8.0056409835815
Time Spent for AutoCommit: 35.4979159832
Time Spent for autocommit with bind: 33.303519010544

0

Bu benim için çalıştı

$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

Böyle bir şeye ne dersin:

        if(count($types_of_values)>0){
         $uid = 1;
         $x = 0;
         $sql = "";
         $values = array();
          foreach($types_of_values as $k=>$v){
            $sql .= "(:id_$k,:kind_of_val_$k), ";
            $values[":id_$k"] = $uid;
            $values[":kind_of_val_$k"] = $v;
          }
         $sql = substr($sql,0,-2);
         $query = "INSERT INTO table (id,value_type) VALUES $sql";
         $res = $this->db->prepare($query);
         $res->execute($values);            
        }

Bunun arkasındaki fikir, dizi ifadeleriniz arasında geçiş yapmak ve hazırlanmış deyim yer tutucularınız için her bir döngüye "kimlik numaraları" eklemek, aynı zamanda bağlayıcı parametreler için değerleri dizinize eklemek. Diziden "anahtar" dizinini kullanmaktan hoşlanmıyorsanız, döngü içine $ i = 0 ve $ i ++ ekleyebilirsiniz. Bu örnekte, adlandırılmış anahtarlarla ilişkilendirilebilir dizileriniz olsa bile, anahtarların benzersiz olması koşuluyla yine de çalışır. Küçük bir çalışma ile iç içe diziler için de iyi olur.

** Substr, $ sql değişkenlerini son boşluk ve virgülle ayırır, eğer bir alanınız yoksa bunu -2 yerine -1 olarak değiştirmeniz gerekir.


-1

Hazırlanan sorguyu oluşturmak için burada verilen çözümlerin çoğu olması gerektiği kadar karmaşıktır. PHP'nin yerleşik işlevlerini kullanarak, önemli bir ek yük olmadan SQL deyimini kolayca oluşturabilirsiniz.

$recordsHer kaydın kendisinin dizine alınmış bir dizi olduğu bir kayıt dizisi göz önüne alındığında , field => valueaşağıdaki işlev, kayıtları tek bir hazırlanmış deyim kullanarak $tablebir PDO bağlantısında verilen tabloya ekleyecektir $connection. Aşağıdaki çağrıda argüman açma argüman kullanımı nedeniyle bunun bir PHP 5.6+ çözüm olduğunu unutmayın 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
SQL koduna karşı savunmasız olduğu için bu kod asla kullanılmamalıdır
Common Sense
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.