PDO Prepared Fügt mehrere Zeilen in eine einzelne Abfrage ein


145

Ich verwende derzeit diese Art von SQL unter MySQL, um mehrere Wertezeilen in eine einzige Abfrage einzufügen:

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

Bei den Lesungen zu PDO sollten die vorbereiteten Anweisungen zur Verwendung eine bessere Sicherheit bieten als statische Abfragen.

Ich würde daher gerne wissen, ob es möglich ist, mit vorbereiteten Anweisungen "Einfügen mehrerer Wertzeilen mithilfe einer Abfrage" zu generieren.

Wenn ja, kann ich wissen, wie ich es implementieren kann?


vorsichtig mit vielen Antworten für $stmt->execute($data); php.net/manual/en/… Grundsätzlich werden alle Parameter als Strings validiert übergeben. Durchlaufen Sie einfach die Daten, nachdem Sie die Abfrage erstellt haben, und übergeben Sie den Typ manuell bindValueoder bindParamübergeben Sie ihn als drittes Argument.
MrMesees

Antworten:


150

Mehrere Werte mit PDO-vorbereiteten Anweisungen einfügen

Einfügen mehrerer Werte in eine Ausführungsanweisung. Warum, weil es laut dieser Seite schneller ist als normale Einfügungen.

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

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

mehr Datenwerte oder Sie haben wahrscheinlich eine Schleife, die Daten auffüllt.

Bei vorbereiteten Einfügungen müssen Sie die Felder kennen, in die Sie einfügen, und die Anzahl der Felder, um die? Platzhalter zum Binden Ihrer Parameter.

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

So soll die insert-Anweisung im Grunde aussehen.

Nun der Code:

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

Obwohl es in meinem Test nur einen Unterschied von 1 Sekunde gab, wenn mehrere Einsätze und regelmäßig vorbereitete Einsätze mit einem einzigen Wert verwendet wurden.


4
Ein Tippfehler erwähnt in der obigen Erklärung $ datafields, obwohl $ datafield in $ sql verwendet wird. Das Kopieren und Einfügen würde also zu einem Fehler führen. Bitte korrigieren Sie. Vielen Dank für diese Lösung.
Pal4life

1
Wenn Sie dies eine Weile verwendet haben, haben Sie festgestellt, dass Werte mit einfachen Anführungszeichen nicht ordnungsgemäß maskiert werden. Die Verwendung von doppelten Anführungszeichen für Implosion wirkt für mich wie ein Zauber: $ a [] = '("'. Implode (", ", $ question_marks). '", NOW ())';
QWERZMAN

1
array_merge scheint teurer zu sein als nur die Verwendung eines array_push.
K2xL

14
Wenn Sie sagen "Es gab nur einen Unterschied von 1 Sekunde", wie viele Zeilen haben Sie Daten eingefügt? 1 Sek. Ist je nach Kontext ziemlich wichtig.
Kevin Dice

3
Optimierung: Es macht keinen Sinn, placeholders()immer wieder anzurufen. Rufen Sie es einmal vor der Schleife mit auf sizeof($datafields)und hängen Sie die Ergebniszeichenfolge an $question_marks[]die Schleife an.
AVIDeveloper

71

Gleiche Antwort wie Herr Balagtas, etwas klarer ...

Neuere Versionen von MySQL und PHP PDO tun Unterstützung mehrreihigen INSERTAussagen.

SQL-Übersicht

Das SQL sieht ungefähr so ​​aus, vorausgesetzt, Sie möchten eine 3-Spalten-Tabelle erstellen INSERT.

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

ON DUPLICATE KEY UPDATEfunktioniert wie erwartet auch mit einem mehrzeiligen INSERT; füge dies hinzu:

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

PHP Übersicht

Ihr PHP-Code folgt dem üblichen $pdo->prepare($qry)und$stmt->execute($params) PDO-Aufrufen.

$paramswird ein eindimensionales Array aller Werte sein, die an das übergeben werden sollenINSERT .

Im obigen Beispiel sollte es 9 Elemente enthalten. PDO verwendet jeden 3er-Satz als einzelne Wertereihe. (Einfügen von 3 Zeilen mit jeweils 3 Spalten = 9 Elementarray.)

Implementierung

Der folgende Code wurde aus Gründen der Klarheit und nicht der Effizienz geschrieben. Arbeite mit dem PHParray_*() Funktionen, um Ihre Daten besser abzubilden oder zu durchsuchen, wenn Sie möchten. Ob Sie Transaktionen verwenden können, hängt natürlich von Ihrem MySQL-Tabellentyp ab.

Angenommen:

  • $tblName - Der Zeichenfolgenname der Tabelle, in die INSERT eingefügt werden soll
  • $colNames- 1-dimensionales Array der Spaltennamen der Tabelle Diese Spaltennamen müssen gültige MySQL-Spaltenbezeichner sein. Entkomme ihnen mit Backticks (``), wenn sie es nicht sind
  • $dataVals - mehrdimensionales Array, wobei jedes Element ein 1-d-Array einer Wertezeile für INSERT ist

Beispielcode

// 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
Das ist wirklich schade, dass PDO dies so handhabt. Es gibt einige sehr elegante Möglichkeiten, dies in anderen DB-Treibern zu tun.
Jonathon

Dadurch werden die Platzhalter noch knapper und müssen $rowPlacesnicht mehr benötigt werden:$allPlaces = implode(',', array_fill(0, count($dataVals), '('.str_pad('', (count($colNames)*2)-1, '?,').')'));
Phil

Funktioniert perfekt. Ich möchte dieser Antwort die Notwendigkeit hinzufügen, die Eindeutigkeit der (Kombination von) Indizes in der Tabelle sicherzustellen. Wie in ALTER TABLE votesADD UNIQUE unique_index( user, email, address);
Giuseppe

1
Genial! Übrigens wird die Verwendung array_push($dataToInsert, ...array_values($dataVals));dann viel schneller seinforeach ($dataVals as $row => $data) {}
Anis

39

Für das, was es wert ist, haben viele Benutzer empfohlen, INSERT-Anweisungen zu durchlaufen, anstatt wie die ausgewählte Antwort eine einzelne Zeichenfolgenabfrage zu erstellen. Ich habe beschlossen, einen einfachen Test mit nur zwei Feldern und einer sehr einfachen Einfügeanweisung durchzuführen:

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

Während die Gesamtabfrage selbst Millisekunden oder weniger dauerte, war die letztere Abfrage (einzelne Zeichenfolge) durchweg achtmal schneller oder länger. Wenn dies so aufgebaut wäre, dass es einen Import von Tausenden von Zeilen in viel mehr Spalten widerspiegelt, könnte der Unterschied enorm sein.


@ JM4 - tolle Idee, 10 Zeilen direkt in eine Ausführung zu setzen . Aber wie kann ich Tausende von Zeilen einfügen, wenn sie in einem Objekt wie JSON gespeichert sind? Mein Code unten funktioniert einwandfrei. Aber wie kann ich es anpassen, um 10 Zeilen in eine Ausführung einzufügen? `foreach ($ json_content als $ datarow) {$ id = $ datarow [id]; $ date = $ datarow [Datum]; $ row3 = $ datarow [row3]; $ row4 = $ datarow [row4]; $ row5 = $ datarow [row5]; $ row6 = $ datarow [row6]; $ row7 = $ datarow [row7]; // führe jetzt $ databaseaseinsert-> execute () aus; } // Ende von foreach `
Peter

@ JM4 - ... und meine zweite Frage lautet: "Warum gibt es bind_paramin der zweiten Importroutine keine Anweisung"?
Peter

Müssten Sie nicht zweimal schleifen? Sie müssten das auch dynamisch generieren (?,?), oder?
NoobishPro

@NoobishPro Ja, Sie können dasselbe für / foreach verwenden, um beide zu generieren.
Chazy Chaz

34

Die akzeptierte Antwort von Herbert Balagtas funktioniert gut, wenn das $ data-Array klein ist. Bei größeren $ data-Arrays wird die Funktion array_merge unerschwinglich langsam. Meine Testdatei zum Erstellen des $ data-Arrays enthält 28 Spalten und ungefähr 80.000 Zeilen. Das endgültige Skript dauerte 41 Sekunden .

Die Verwendung von array_push () zum Erstellen von $ insert_values ​​anstelle von array_merge () führte zu einer 100-fachen Beschleunigung mit einer Ausführungszeit von 0,41 s .

Das problematische array_merge ():

$insert_values = array();

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

Um die Notwendigkeit von array_merge () zu beseitigen, können Sie stattdessen die folgenden zwei Arrays erstellen:

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

Diese Arrays können dann wie folgt verwendet werden:

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
In PHP 5.6 können Sie array_push($data, ...array_values($row))stattdessen tun $data = array_merge($data, array_values($row));. Viel schneller.
Mpen

Warum 5.6? Die Dokumentation sagt nichts über 5.6 aus, array_push()ist sogar in PHP 4 verfügbar.
ZurabWeb

1
@Piero ist nur PHP 5.6+ Code, nicht wegen der Verwendung von array_push(), sondern weil @Mark das Entpacken von Argumenten verwendet. Beachten Sie den ...array_values()Anruf dort?
mariano.iglesias

@ mariano.iglesias array_values()ist auch in PHP 4 verfügbar. Ich bin mir nicht sicher, ob du das meinst argument unpacking.
ZurabWeb

2
@Piero, das Entpacken von Argumenten ist eine Funktion, die in PHP 5.6 eingeführt wurde. Auf diese Weise können mehrere Argumente als Array bereitgestellt werden. Überprüfen Sie hier - php.net/manual/en/…
Anis

14

Zwei mögliche Ansätze:

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

Oder:

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

Wenn sich die Daten für alle Zeilen in einem einzigen Array befinden, würde ich die zweite Lösung verwenden.


10
In letzterem Fall führen Sie dann nicht mehrere (möglicherweise Tausende) separate Ausführungsaufrufe durch, anstatt sie zu einer Anweisung zu kombinieren?
JM4

@ JM4, schlagen Sie $stmt->execute();vor, außerhalb der foreach-Schleife zu sein?
bafromca

@bafromca - Ja, das bin ich. Siehe meine Antwort oben mit positiven Stimmen. Bei einer reinen Insert-Anweisung gibt es keinen Grund, warum ich logischerweise feststellen kann, dass es sich nicht um eine einzelne Anweisung handeln kann. Ein Aufruf, eine Ausführung. Tatsächlich könnte meine Antwort von Anfang 2012 noch weiter verbessert werden - etwas, das ich später tun werde, wenn ich mehr Zeit habe. Wenn Sie anfangen, Kombinationen zum Einfügen / Aktualisieren / Löschen zu verwenden, ist das eine andere Geschichte.
JM4

12

So verwenden Sie einfach keine vorbereiteten Anweisungen.

Es ist vollkommen in Ordnung, eine Zeile pro Abfrage einzufügen, da Sie eine vorbereitete Anweisung mehrmals mit unterschiedlichen Parametern ausführen können. Dies ist in der Tat einer der größten Vorteile, da Sie auf effiziente, sichere und komfortable Weise eine große Anzahl von Zeilen einfügen können.

Es ist also möglicherweise möglich, das von Ihnen vorgeschlagene Schema zumindest für eine feste Anzahl von Zeilen zu implementieren, aber es ist fast garantiert, dass dies nicht wirklich das ist, was Sie wollen.


1
Können Sie einen besseren Weg vorschlagen, um mehrere Zeilen in eine Tabelle einzufügen?
Crashthatch

@Crashthatch: Mach es einfach naiv: Richte die vorbereitete Anweisung einmal ein und führe sie dann für jede Zeile mit unterschiedlichen Werten für die gebundenen Parameter aus. Das ist der zweite Ansatz in Zyks Antwort.
sebasgo

2
Der Zweck, den Sie für die vorbereitete Erklärung erwähnt haben, ist richtig. Die Verwendung von Multi-Insert ist jedoch eine andere Technik zur Verbesserung der Einfügegeschwindigkeit und kann auch mit vorbereiteten Anweisungen verwendet werden. Nach meiner Erfahrung war bei der Migration von 30 Millionen Zeilendaten mithilfe einer von PDO vorbereiteten Anweisung die Mehrfacheinfügung 7 bis 10 Mal schneller als die Gruppierung von Einzeleinfügungen in Transaktionen.
Anis

1
Stimme Anis absolut zu. Ich habe 100.000 Reihen und bekomme eine enorme Geschwindigkeitssteigerung mit Muli-Reihen-Einsätzen.
Kenneth

Die Behauptung, dass das Aufrufen einer relationalen Datenbank in einer Schleife einmal pro Zeile im Allgemeinen eine gute Sache ist, kann ich nicht zustimmen. Dafür stimmen. Zugegeben, manchmal ist es in Ordnung. Ich glaube nicht an Absolutes mit Engineering. Dies ist jedoch ein Anti-Muster, das nur in ausgewählten Fällen verwendet werden sollte.
Brandon

8

Eine kürzere Antwort: Reduzieren Sie dann das nach Spalten geordnete Datenarray

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

Wenn Sie etwa 1.000 Datensätze einfügen, müssen Sie nicht jeden Datensatz durchlaufen, um sie einzufügen, wenn Sie nur die Anzahl der Werte benötigen.


5

Hier ist mein einfacher Ansatz.

    $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
Sie vereiteln den Punkt, vorbereitete Anweisungen zu verwenden. Die On the readings on PDO, the use prepared statements should give me a better security than static queries.
Operation

2
Nur Bilder, die Sie nicht validiert haben $workouts_id, die $values mit ziemlich unerwarteten Daten haben können. Sie können nicht garantieren, dass dies möglicherweise nicht jetzt, aber in Zukunft ein anderer Entwickler diese Daten unsicher macht. Daher halte ich die von PDO vorbereitete Abfrage für viel richtiger.
Nikita_kharkov_ua

3

Hier ist eine Klasse, die ich geschrieben habe, um mehrere Einfügungen mit Löschoption durchzuführen:

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

Hallo Pierre. Vielleicht bist du hier nicht mehr aktiv. Trotzdem wollte ich nur darauf hinweisen, dass meine Idee für diese Ausgabe fast identisch mit Ihrer aussieht. Reiner Zufall, da ich denke, dass dazu nicht viel mehr gehört. Ich habe auch Klassen für DELETE- UND UPDATE-Operationen hinzugefügt und anschließend einige Ideen von hier eingebunden. Ich habe deine Klasse einfach nicht gesehen. Bitte entschuldigen Sie meine schamlose Eigenwerbung hier, aber ich denke, es wird jemandem helfen. Hoffe das ist nicht gegen SO-Regeln. Finden Sie es hier .
JackLeEmmerdeur

1

So habe ich es gemacht:

Definieren Sie zuerst die Spaltennamen, die Sie verwenden möchten, oder lassen Sie sie leer, und pdo geht davon aus, dass Sie alle Spalten in der Tabelle verwenden möchten. In diesem Fall müssen Sie die Zeilenwerte in der genauen Reihenfolge angeben, in der sie in der Tabelle angezeigt werden .

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

Angenommen, Sie haben bereits ein zweidimensionales Array vorbereitet. Iterieren Sie es und erstellen Sie eine Zeichenfolge mit Ihren Zeilenwerten als solche:

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

Jetzt haben Sie nur überprüft, ob $ rows bereits definiert wurde. Wenn nicht, erstellen Sie es und speichern Sie die Zeilenwerte und die erforderliche SQL-Syntax, damit es sich um eine gültige Anweisung handelt. Beachten Sie, dass Zeichenfolgen in doppelte und einfache Anführungszeichen gesetzt werden sollten, damit sie sofort als solche erkannt werden.

Sie müssen lediglich die Anweisung vorbereiten und als solche ausführen:

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

Bisher mit bis zu 2000 Zeilen getestet, und die Ausführungszeit ist schlecht. Ich werde noch einige Tests durchführen und hierher zurückkehren, falls ich noch etwas beitragen kann.

Grüße.


1

Da dies noch nicht vorgeschlagen wurde, bin ich mir ziemlich sicher, dass LOAD DATA INFILE immer noch der schnellste Weg ist, Daten zu laden, da es die Indizierung deaktiviert, alle Daten einfügt und dann die Indizes wieder aktiviert - alles in einer einzigen Anforderung.

Das Speichern der Daten als CSV sollte unter Berücksichtigung von fputcsv ziemlich trivial sein. MyISAM ist am schnellsten, aber in InnoDB erhalten Sie immer noch eine große Leistung. Es gibt jedoch noch andere Nachteile, daher würde ich diesen Weg gehen, wenn Sie viele Daten einfügen und sich nicht mit weniger als 100 Zeilen beschäftigen.


1

Obwohl mir eine alte Frage alle Beiträge sehr geholfen haben, ist hier meine Lösung, die in meiner eigenen DbContextKlasse funktioniert . Der $rowsParameter ist einfach ein Array von assoziativen Arrays, die Zeilen oder Modelle darstellen : field name => insert value.

Wenn Sie ein Muster verwenden, das Modelle verwendet, passt dies gut, wenn Modelldaten als Array übergeben werden, z. B. von einer ToRowArrayMethode innerhalb der Modellklasse.

Hinweis : Es sollte selbstverständlich sein, aber niemals zuzulassen, dass die an diese Methode übergebenen Argumente dem Benutzer zugänglich gemacht werden oder von anderen Benutzereingaben als den Einfügungswerten abhängig sind, die validiert und bereinigt wurden. Das $tableNameArgument und die Spaltennamen sollten durch die aufrufende Logik definiert werden. Beispielsweise könnte ein UserModell der Benutzertabelle zugeordnet werden, deren Spaltenliste den Mitgliedsfeldern des Modells zugeordnet ist.

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

eine Transaktion loswerden, da es keinen Sinn macht, eine für eine einzelne Abfrage zu verwenden. und wie üblich ist dieser Code anfällig für SQL-Injection- oder Abfragefehler.
Ihr gesunder Menschenverstand

Sie haben Recht mit der redundanten Verwendung von Transaktionen für diesen Fall, aber ich sehe nicht, wie anfällig dies für SQL-Injection ist. Es ist parametrisiert, so dass ich nur annehmen kann, dass Sie davon ausgehen, dass $tableNamees dem Benutzer ausgesetzt ist, was es nicht ist, es ist in der DAL. Können Sie Ihre Ansprüche erweitern? Es ist nicht hilfreich, nur Dinge zu sagen.
Lee

Nun, es ist nicht nur ein Tabellenname, sondern auch: Wie können Sie wissen, ob er von jemandem angezeigt wird, der den hier veröffentlichten Code verwenden würde?
Ihr gesunder Menschenverstand

Es liegt also in der Verantwortung eines Posters, jede mögliche Verwendung des Codes oder jede Quelle für Argumente zu skizzieren? Vielleicht habe ich höhere Erwartungen an Menschen. Würde es Sie glücklicher machen, wenn ich eine Notiz hinzufügen würde, damit der Benutzer keinen Zugriff darauf hat $tableName?
Lee

Es liegt in der Verantwortung eines Posters, einen zuverlässigen Code zu veröffentlichen, wenn es darum geht, jemandem zu helfen, nicht nur anzugeben.
Ihr gesunder Menschenverstand

1

Hier ist eine andere (schlanke) Lösung für dieses Problem:

Zuerst müssen Sie die Daten des Quellarrays (hier: $ aData) mit count () zählen. Dann verwenden Sie array_fill () und generieren ein neues Array mit so vielen Einträgen wie das Quellarray mit dem Wert "(?,?)" (Die Anzahl der Platzhalter hängt von den verwendeten Feldern ab; hier: 2). Dann muss das erzeugte Array implodiert werden und als Klebstoff wird ein Komma verwendet. Innerhalb der foreach-Schleife müssen Sie einen weiteren Index für die Anzahl der verwendeten Platzhalter generieren (Anzahl der Platzhalter * aktueller Array-Index + 1). Sie müssen dem generierten Index nach jedem gebundenen Wert 1 hinzufügen.

$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

Mit dieser Funktion können Sie mehrere Zeilen in eine einzelne Abfrage einfügen:

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 ist ein Array von Arrays von Werten. In Ihrem Fall würden Sie die Funktion mit aufrufen

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

Dies hat den Vorteil, dass Sie vorbereitete Anweisungen verwenden , während Sie mehrere Zeilen mit einer einzigen Abfrage einfügen. Sicherheit!



0

Mein Beispiel aus der Praxis, um alle deutschen Postleitzahlen in eine leere Tabelle einzufügen (um später Städtenamen hinzuzufügen):

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

Wie Sie sehen können, ist es völlig flexibel. Sie müssen weder die Anzahl der Spalten noch die Position Ihrer Spalte überprüfen. Sie müssen nur die Einfügedaten einstellen:

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

Ich bin stolz auf einige der Abfragezeichenfolgenkonstruktoren, da sie ohne umfangreiche Array-Funktionen wie array_merge funktionieren. Besonders vsprintf () war ein guter Fund.

Schließlich musste ich 2x while () hinzufügen, um das Speicherlimit nicht zu überschreiten. Dies hängt von Ihrem Speicherlimit ab, ist aber eine gute allgemeine Lösung, um Probleme zu vermeiden (und 10 Abfragen sind immer noch viel besser als 10.000).


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

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

}
?>

Willkommen bei stackoverflow. Nicht nur den Code, bitte posten Sie Ihr Problem und erklären Sie es.
Prakash Palnati

Grundsätzlich. Es ist nur eine Implementierung des Codes in der akzeptierten Antwort
Your Common Sense

0

Ich hatte das gleiche Problem und so erreiche ich es für mich selbst, und ich habe eine Funktion für mich selbst dafür gemacht (und Sie können es verwenden, wenn das Ihnen hilft).

Beispiel:

INSERT IN LÄNDER (Land, Stadt) WERTE (Deutschland, Berlin), (Frankreich, 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;
    }

Wenn insertMultipleData ($ table, $ multi_params) TRUE zurückgibt , wurden Ihre Daten in Ihre Datenbank eingefügt.


0

Basierend auf meinen Experimenten fand ich heraus, dass die MySQL-Insert-Anweisung mit mehreren Wertzeilen in einer einzelnen Transaktion die schnellste ist.

Wenn die Daten jedoch zu groß sind, kann die max_allowed_packetEinstellung von mysql die Einfügung einer einzelnen Transaktion mit mehreren Wertzeilen einschränken. Daher schlagen die folgenden Funktionen fehl, wenn Daten vorhanden sind, die größer als MySQL max_allowed_packetsind:

  1. singleTransactionInsertWithRollback
  2. singleTransactionInsertWithPlaceholders
  3. singleTransactionInsert

Das erfolgreichste Szenario zum Einfügen großer Datenmengen ist die transactionSpeedMethode, die jedoch mehr Zeit in Anspruch nimmt als die oben genannten Methoden. Um dieses Problem zu lösen, können Sie Ihre Daten entweder in kleinere Blöcke aufteilen und die Einfügung einer einzelnen Transaktion mehrmals aufrufen oder die Ausführungsgeschwindigkeit mithilfe der transactionSpeedMethode aufgeben .

Hier ist meine Forschung

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

Die Ergebnisse für 100.000 Einträge für eine Tabelle mit nur zwei Spalten sind wie folgt

$ 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

Das hat bei mir funktioniert

$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

was ist mit so etwas:

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

Die Idee dahinter ist, durch Ihre Array-Werte zu blättern und jeder Schleife "ID-Nummern" für Ihre vorbereiteten Anweisungsplatzhalter hinzuzufügen, während Sie gleichzeitig die Werte für die Bindungsparameter zu Ihrem Array hinzufügen. Wenn Sie den "Schlüssel" -Index aus dem Array nicht verwenden möchten, können Sie $ i = 0 und $ i ++ in die Schleife einfügen. Beides funktioniert in diesem Beispiel, selbst wenn Sie assoziative Arrays mit benannten Schlüsseln haben, würde es trotzdem funktionieren, vorausgesetzt, die Schlüssel sind eindeutig. Mit ein wenig Arbeit wäre es auch für verschachtelte Arrays in Ordnung.

** Beachten Sie, dass substr das letzte Leerzeichen und Komma der $ sql-Variablen entfernt. Wenn Sie kein Leerzeichen haben, müssen Sie dies in -1 anstatt in -2 ändern.


-1

Die meisten der hier angegebenen Lösungen zum Erstellen der vorbereiteten Abfrage sind komplexer als erforderlich. Mit den in PHP integrierten Funktionen können Sie die SQL-Anweisung problemlos und ohne erheblichen Aufwand erstellen.

Bei $recordseinem Array von Datensätzen, bei denen jeder Datensatz selbst ein indiziertes Array (in Form von field => value) ist, fügt die folgende Funktion die Datensätze in der angegebenen Tabelle $tableüber eine PDO-Verbindung $connectionmit nur einer einzigen vorbereiteten Anweisung ein. Beachten Sie, dass dies eine PHP 5.6+ -Lösung ist, da beim Aufruf von 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
Dieser Code sollte niemals verwendet werden, da er für SQL-Injection anfällig ist
Your Common Sense
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.