wstaw wiele wierszy poprzez tablicę php do mysql


129

Przekazuję duży zestaw danych do tabeli MySQL za pośrednictwem PHP za pomocą poleceń wstawiania i zastanawiam się, czy można wstawić około 1000 wierszy na raz za pomocą zapytania innego niż dołączanie każdej wartości na końcu ciągu o długości mili, a następnie wykonanie go. Używam frameworka CodeIgniter, więc jego funkcje są również dostępne dla mnie.


Udzieliłem odpowiedzi zgodnie z Twoim pytaniem dotyczącym wielowierszowej wstawki Codeigniter.
Somnath Muluk,

@SomnathMuluk Dziękuję, ale minęło trochę czasu, odkąd potrzebowałem odpowiedzieć na to pytanie
:)

Polecam użycie funkcji insert_batch CodeIgnitera. Jeśli korzystasz z biblioteki, zawsze staraj się wykorzystać jej mocne strony i standardy kodowania.
Dewald Els

Wierzę wkładka partia jest najlepszym sposobem na to zobaczyć link stackoverflow.com/questions/27206178/codeigniter-insert-batch
Syed Amir Bukhari

Odpowiedzi:


237

Składanie jednej INSERTinstrukcji z wieloma wierszami jest znacznie szybsze w MySQL niż jednej INSERTinstrukcji na wiersz.

To powiedziawszy, wygląda na to, że możesz mieć problemy z obsługą ciągów znaków w PHP, co jest tak naprawdę problemem algorytmu, a nie języka. Zasadniczo podczas pracy z dużymi ciągami znaków chcesz zminimalizować niepotrzebne kopiowanie. Przede wszystkim oznacza to, że chcesz uniknąć konkatenacji. Najszybszym i najbardziej wydajnym pod względem pamięci sposobem zbudowania dużego ciągu, na przykład wstawiania setek wierszy w jednym, jest wykorzystanie implode()funkcji i przypisania tablicy.

$sql = array(); 
foreach( $data as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $sql));

Zaletą tego podejścia jest to, że nie kopiujesz i nie kopiujesz ponownie instrukcji SQL, które do tej pory składałeś przy każdym konkatenacji; zamiast tego PHP robi to raz w implode()instrukcji. To wielka wygrana.

Jeśli masz wiele kolumn do złożenia, a jedna lub więcej jest bardzo długich, możesz również zbudować wewnętrzną pętlę, aby zrobić to samo i użyć implode()do przypisania klauzuli value do tablicy zewnętrznej.


5
Dziękuję za to! Przy okazji brakuje ci nawiasu zamykającego na końcu funkcji, jeśli ktoś planuje ją skopiować. mysql_real_query ('INSERT INTO table VALUES (tekst, kategoria)' .implode (','. $ sql));
toofarsideways

3
Dzięki! Naprawiony. (Często to robię ...)
staticsan

1
a zapytanie powinno naprawdę brzmieć „INSERT INTO table (text, category) VALUES” .implode („,”. $ sql) ”kodowanie o czwartej nad ranem prowadzi do strasznego debugowania :(
toofarsideways

3
Wierzę, że ten kod stworzy rozwiązanie dla mojego najnowszego projektu. Moje pytanie brzmi, czy jest to bezpieczne przed wstrzyknięciem SQL? Moje plany są, aby przełączyć się mysql_real_escape_stringz mysqli_real_escape_stringi mysql_queryz mysqli_queryjak używam mysqli i zostały one przestarzałe jak z PHP5. Wielkie dzięki!
wordman

2
mysql_*został usunięty z PHP, więc upewnij się, że korzystasz z mysqli_*interfejsu.
Rick James

60

Codeigniter obsługuje teraz wielokrotne wstawianie / wstawianie partii. Miałem ten sam problem. Chociaż jest już bardzo późno na odpowiedź, to komuś pomoże. Dlatego odpowiadając na to pytanie.

$data = array(
   array(
      'title' => 'My title' ,
      'name' => 'My Name' ,
      'date' => 'My date'
   ),
   array(
      'title' => 'Another title' ,
      'name' => 'Another Name' ,
      'date' => 'Another date'
   )
);

$this->db->insert_batch('mytable', $data);

// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'), ('Another title', 'Another name', 'Another date')

2
Myślę, że jest to najbardziej zalecany sposób wykonywania wstawiania wielu wierszy, a nie mysql_query. Ponieważ kiedy używamy frameworka, dobrą praktyką jest zawsze używanie wbudowanych funkcji frameworka.
Praneeth Nidarshan

22

Możesz przygotować zapytanie do wstawienia jednego wiersza za pomocą klasy mysqli_stmt, a następnie iterować po tablicy danych. Coś jak:

$stmt =  $db->stmt_init();
$stmt->prepare("INSERT INTO mytbl (fld1, fld2, fld3, fld4) VALUES(?, ?, ?, ?)");
foreach($myarray as $row)
{
    $stmt->bind_param('idsb', $row['fld1'], $row['fld2'], $row['fld3'], $row['fld4']);
    $stmt->execute();
}
$stmt->close();

Gdzie „idsb” to typy danych, które wiążesz (int, double, string, blob).


6
Niedawno przeprowadziłem testy porównujące wstawianie zbiorcze i przygotowane instrukcje wstawiania, jak wspomniano tutaj. Dla około 500 wkładek, metoda gotowych wkładek zakończyła się w ciągu 2,6-4,4 sekundy, a metoda wkładek zbiorczych w ciągu 0,12-0,35 sekundy. Wydawało mi się, że mysql „zebrałby” te przygotowane instrukcje razem i działałby równie dobrze, jak zbiorcze wstawianie, ale w domyślnej konfiguracji różnica w wydajności jest najwyraźniej ogromna. (Przy okazji wszystkie zapytania testowe były uruchamiane w ramach jednej transakcji dla każdego testu, aby zapobiec automatycznemu
zatwierdzaniu

16

Wiem, że to stare zapytanie, ale właśnie czytałem i pomyślałem, że dodam to, co znalazłem gdzie indziej:

mysqli w PHP 5 to ojbect z kilkoma dobrymi funkcjami, które pozwolą Ci przyspieszyć czas wstawiania odpowiedzi powyżej:

$mysqli->autocommit(FALSE);
$mysqli->multi_query($sqlCombined);
$mysqli->autocommit(TRUE);

Wyłączenie autocommit podczas wstawiania wielu wierszy znacznie przyspiesza wstawianie, więc wyłącz je, a następnie wykonaj, jak wspomniano powyżej, lub po prostu utwórz ciąg (sqlCombined), który składa się z wielu instrukcji wstawiania oddzielonych średnikami i wielu zapytań poradzi sobie z nimi dobrze.

Mam nadzieję, że pomoże to komuś zaoszczędzić czas (wyszukiwanie i wstawianie!)

R


Oto błąd, który otrzymałem podczas korzystania z Twojego pomysłu: „Błąd krytyczny: wywołanie funkcji składowej autocommit () o wartości null w /homepages/25/d402746174/htdocs/MoneyMachine/saveQuotes.php on line 30”
user3217883

8

Zawsze możesz użyć mysql LOAD DATA:

LOAD DATA LOCAL INFILE '/full/path/to/file/foo.csv' INTO TABLE `footable` FIELDS TERMINATED BY ',' LINES TERMINATED BY '\r\n' 

do zbiorczego wstawiania zamiast używania zestawu INSERTinstrukcji.


Przyjrzałem się temu, ale przed wstawieniem muszę manipulować danymi. Został mi dany jako iloczyn kartezjański zbioru wartości int 1400 na 1400, z których wiele jest zerowych. Muszę przekonwertować to na relację wiele do wielu przy użyciu tabeli pośredniej, aby zaoszczędzić miejsce, stąd potrzeba wkładek w przeciwieństwie do wstawiania zbiorczego
toofarsideways Kwietnia

Zawsze możesz wygenerować plik csv po zmanipulowaniu go i wywołaniu instrukcji mysql, która ładuje dane
Alexander Jardim

Myślę, że warto wiedzieć, że ścieżka jest lokalna dla klienta SQL, a nie dla serwera SQL. Plik jest przesyłany na serwer, a następnie przez niego odczytywany. Myślałem, że plik musiał już znajdować się na serwerze, co nie jest prawdą. Jeśli jest już na serwerze, usuń LOCALbit.
Kyle

5

Cóż, nie chcesz wykonywać 1000 zapytań, ale zrobienie tego jest w porządku:

$stmt= array( 'array of statements' );
$query= 'INSERT INTO yourtable (col1,col2,col3) VALUES ';
foreach( $stmt AS $k => $v ) {
  $query.= '(' .$v. ')'; // NOTE: you'll have to change to suit
  if ( $k !== sizeof($stmt)-1 ) $query.= ', ';
}
$r= mysql_query($query);

W zależności od źródła danych wypełnienie tablicy może być tak proste, jak otwarcie pliku i zrzucenie zawartości do tablicy za pośrednictwem file().


1
Jest to czystsze, jeśli przeniesiesz to powyżej zapytania i zmienisz je na coś takiego jak if ($ k> 0).
cherouvim

@cherouvim ... Cóż, masz rację. Dzięki za wkład. Kiedy ponownie czytam podany przeze mnie przykład, nie rozumiem twojego punktu widzenia. Staraj się rozwinąć (przez pastebin itp.). Dzięki-
bdl

3
$query= array(); 
foreach( $your_data as $row ) {
    $query[] = '("'.mysql_real_escape_string($row['text']).'", '.$row['category_id'].')';
}
mysql_query('INSERT INTO table (text, category) VALUES '.implode(',', $query));

1

Możesz to zrobić na kilka sposobów w Codeigniter np

Najpierw pętlą

foreach($myarray as $row)
{
   $data = array("first"=>$row->first,"second"=>$row->sec);
   $this->db->insert('table_name',$data);
}

Po drugie - przez wstawianie partii

$data = array(
       array(
          'first' => $myarray[0]['first'] ,
          'second' => $myarray[0]['sec'],
        ),
       array(
          'first' => $myarray[1]['first'] ,
          'second' => $myarray[1]['sec'],
        ),
    );

    $this->db->insert_batch('table_name', $data);

Trzeci sposób - przez podanie wielu wartości

$sql = array(); 
foreach( $myarray as $row ) {
    $sql[] = '("'.mysql_real_escape_string($row['first']).'", '.$row['sec'].')';
}
mysql_query('INSERT INTO table (first, second) VALUES '.implode(',', $sql));

1

Chociaż na to pytanie jest już za późno. Oto moja odpowiedź na to samo.

Jeśli używasz CodeIgniter, możesz użyć wbudowanych metod zdefiniowanych w klasie query_builder.

$ this-> db-> insert_batch ()

Generuje ciąg wstawiania na podstawie dostarczonych danych i uruchamia zapytanie. Do funkcji można przekazać tablicę lub obiekt. Oto przykład wykorzystujący tablicę:

$data = array(
    array(
            'title' => 'My title',
            'name' => 'My Name',
            'date' => 'My date'
    ),
    array(
            'title' => 'Another title',
            'name' => 'Another Name',
            'date' => 'Another date'
    )
);

$this->db->insert_batch('mytable', $data);
// Produces: INSERT INTO mytable (title, name, date) VALUES ('My title', 'My name', 'My date'),  ('Another title', 'Another name', 'Another date')

Pierwszy parametr będzie zawierał nazwę tabeli, drugi to asocjacyjna tablica wartości.

Więcej informacji na temat query_builder można znaleźć tutaj


0

Stworzyłem klasę wykonującą wieloliniowość, która jest używana w następujący sposób:

$pdo->beginTransaction();
$pmi = new PDOMultiLineInserter($pdo, "foo", array("a","b","c","e"), 10);
$pmi->insertRow($data);
// ....
$pmi->insertRow($data);
$pmi->purgeRemainingInserts();
$pdo->commit();

gdzie klasa jest zdefiniowana w następujący sposób:

class PDOMultiLineInserter {
    private $_purgeAtCount;
    private $_bigInsertQuery, $_singleInsertQuery;
    private $_currentlyInsertingRows  = array();
    private $_currentlyInsertingCount = 0;
    private $_numberOfFields;
    private $_error;
    private $_insertCount = 0;

    /**
     * Create a PDOMultiLine Insert object.
     *
     * @param PDO $pdo              The PDO connection
     * @param type $tableName       The table name
     * @param type $fieldsAsArray   An array of the fields being inserted
     * @param type $bigInsertCount  How many rows to collect before performing an insert.
     */
    function __construct(PDO $pdo, $tableName, $fieldsAsArray, $bigInsertCount = 100) {
        $this->_numberOfFields = count($fieldsAsArray);
        $insertIntoPortion = "REPLACE 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;
    }
}

0

Aby wstawić wiele wierszy danych, użyj wstawiania wsadu do zapłonnika kodu.

$this->db->insert_batch('tabname',$data_array); // $data_array holds the value to be inserted

0

Stworzyłem tę prostą funkcję, z której możecie z łatwością korzystać. Będziesz musiał przekazać nazwę ($tbl)tabeli, pole tabeli ($insertFieldsArr)z danymi wstawiającymi, tablicą danych ($arr).

insert_batch('table',array('field1','field2'),$dataArray);

    function insert_batch($tbl,$insertFieldsArr,$arr){ $sql = array(); 
    foreach( $arr as $row ) {
        $strVals='';
        $cnt=0;
        foreach($insertFieldsArr as $key=>$val){
            if(is_array($row)){
                $strVals.="'".mysql_real_escape_string($row[$cnt]).'\',';
            }
            else{
                $strVals.="'".mysql_real_escape_string($row).'\',';
            }
            $cnt++;
        }
        $strVals=rtrim($strVals,',');
        $sql[] = '('.$strVals.')';
    }

    $fields=implode(',',$insertFieldsArr);
    mysql_query('INSERT INTO `'.$tbl.'` ('.$fields.') VALUES '.implode(',', $sql));
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.