Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: PDO + MySQL
Forum PHP.pl > Forum > PHP
mummle
Witam forumowiczów.
Mam pewien zgrzyt z kodem PHP. Od razu uprzedzam - moja znajomość PHP i SQL są na poziomie ... raczkującego dziecka. Stąd prośba o pomoc.
Mam skrypt który ma za zadanie:
1. Insert nowych danych do tabeli.
2. Update istniejących.

Dane pobierane są z pliku CSV. Plik zawiera dane sprzedażowe z 4 okresów, przesyłany jest z końcem pojedynczego okresu rozliczeniowego. Dane z pliku dublują się z danymi w bazie stąd konieczność ich odświeżenia.

Całość skryptu wygląda tak:
  1. <?
  2. $f=$_FILES['r_update'];
  3. $file=$f['tmp_name'];
  4. $c_fname=$f['name'];
  5. $lines=file($file,FILE_SKIP_EMPTY_LINES);
  6. reset($lines);
  7. $newr=0;
  8. $updr=0;
  9.  
  10. // pobranie danych do tablicy $lines z przesłanego pliku CSV z _POST. Ustawienie zmiennych informacyjnych na 0.
  11. try {
  12. $dbk = new PDO('mysql:host=localhost;dbname=baza', 'root', 'root');
  13. $dbk->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  14. $szukaj=explode(";",$lines[0]);
  15. $odd=$szukaj[0];
  16. $issue=$szukaj[4];
  17. $issue_min=$szukaj[4]-5;
  18. $issue_max=$szukaj[4]+5;
  19.  
  20. //Połącznie z bazą. Wydobycie danych do stworzenia widoku/perspektywy.
  21.  
  22. $start=$dbk->query('create view pomocnadlon as select * from r_dane where oddzial like ''.$odd.'' and nr between '.$issue_min.' and '.$issue_max.'');
  23. // tworzę widok/perspektywę by ograniczyć wyszukiwanie w bazie
  24. reset($lines);d
  25.    foreach ($lines as $line) {
  26.            $dane=explode(";", $line);
  27.            for ($i=0;$i<=4;$i++) {
  28.                    $dane[$i]=trim($dane[$i]);
  29.             }
  30. //exploduję linie pliku i wycinam spacje na początku i na końcu
  31.  
  32.        $x="select * from pomocnadlon where id_psd like '".$dane[1]."' and nr=".$dane[4];
  33.        $vstart=$dbk->query($x);
  34.        $vstart->setFetchMode(PDO::FETCH_ASSOC);
  35. //nową instancją istniejącego obiektu PDO pobieram dane o tym czy rekord istnieje
  36.        
  37.        if (empty($vstart)) {
  38.            $insert="INSERT INTO pr_dane (oddzial, id_psd, dost, zwr, nr) VALUES ('".$dane[0]."','".$dane[1]."',".$dane[2].",".$dane[3].",".$dane[4].")";
  39.            $insert_do=$dbk->query($insert);
  40.            $newr++;
  41.        } else {
  42.            $update="update r_dane set oddzial='".$dane[0]."', id_psd='".$dane[1]."', dost=".$dane[2].", zwr=".$dane[3].", nr=".$dane[4]." where oddzial like '".$dane[0]."' and id_psd like '".$dane[1]."' and nr=".$dane[4];
  43.            $update_do=$dbk->query($update);
  44.            $updr++;
  45.        }
  46.    }
  47. //Istnieje - update, czyli ELSE. Nie istnieje bo jest EMPTY - INSERT.
  48.  
  49. $vstart=$dbk->query('drop view pomocnadlon');
  50. echo "Dodano nowe rekordy! ".$newr." <br/>";
  51. echo "Zaktualizowano rekordy! ".$updr." <br/>";
  52. }
  53. catch (PDOException $e) {
  54.    echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();
  55. }
  56. ?>


Plik csv ma postać:
Oddział;ID;Ilość;Niesprzedane;Nr_dostawy. Max 6000 wierszy.

Skrypt pada mi po 300 sekundach od odpalenia, przestawiłem sobie czas wykonania w php.ini na 300. Mam wrażenie, że to co napisałem albo się zapętla, albo nie wyrabia jeśli codzi o skuteczność zapytań. O co chodzi? Jak przyspieszyć UPDATE?

pzdr
mummle
erix
  1. <?php
  2. $lines=file($file,FILE_SKIP_EMPTY_LINES);
  3. ?>

Rób przez pętle z fgets" title="Zobacz w manualu PHP" target="_manual.

Poza tym:
  1. <?php
  2. $update="update r_dane set oddzial='".$dane[0]."', id_psd='".$dane[1]."', dost=".$dane[2].", zwr=".$dane[3].", nr=".$dane[4]." where oddzial like '".$dane[0]."' and id_psd like '".$dane[1]."' and nr=".$dane[4];
  3. ?>

Masz możliwość sprawdzenia bez użycia like?
mummle
Dzięki za podpowiedź z fgets - skorzystam.

Zamieniłem like na = (nie wiem czemu uparłem się na ten sposób porównania), ale nadal po 300 sekundach wywala błąd. Skrypt wciąż bez problemu insertuje dane, gorzej z update.

Pozdrawiam
mummle
Mephistofeles
Korzystasz z PDO! Czemu nie używasz bindowania?
Zaoszczędzisz czasu - wtedy tylko raz wyślesz zapytanie, a cały czas będziesz aktualizował dane i wykonywał zapytanie. Poczytaj bindValue i bindParam.
mummle
Cytat(Mephistofeles @ 15.03.2009, 10:03:08 ) *
Korzystasz z PDO! Czemu nie używasz bindowania?
Zaoszczędzisz czasu - wtedy tylko raz wyślesz zapytanie, a cały czas będziesz aktualizował dane i wykonywał zapytanie. Poczytaj bindValue i bindParam.

A możesz mi podpowiedzieć w jaki sposób mogę to zrobić? Kodem? O bindowaniu wiem, ale nie wiem jak z niego korzystać. Niestety polskie manuale o PDO są dość ubogie (to co znalazłem na wiki mówi, ale nie wyjaśnia, inglisz - chętnie poczytam, przejrzę prototypy obiektów), a poza tym brak im jak i tym z php.net solucji 100% rozwiązań. Opis takiej klasy jest po prostu zbyt ubogi bo jest poglądowy. Chodzi o prezentację a nie o rozwiązania.
Jakbym wiedział jak korzystać z bindowania, co ze sobą niesie to bym nie pytał. Chętnie poznam Twoje źródła wiedzy o PDO. Nie pytam o rybę (choć może w tym przypadku chodzi o nią i jest niezbędna bo czas mnie goni) lecz zapytuję o wędkę, źródła, wiedzę na przyszłość.

Czytałem o bindowaniu parametrów ale nie wiem jak to działa. Pomożesz? Help, need help smile.gif

pzdr
mummle
Mephistofeles
Moje źródła? Wikibooks (PHP), manual (wbrew pozorom jest tam całkiem sporo opisane), i spory opis na WebCity - tutaj.
Bindowanie/podpinanie to operacja na poziomie bazy, więc jest całkowicie odporna na SQL Injection, i czasami szybsza od łączenia parametrów w PHP.
Robi się to tak:
  1. <?php
  2. $stmt = $pdo->prepare('SELECT * FROM xxx WHERE yyy = :yyy'); // Jak widzisz zostawiłem lukę na parametr - :yyy
  3.  
  4. $stmt -> bindValue(':yyy', $y, PDO::PARAM_INT); // Podpinanie wartości - kopiuje zawartość zmiennej, 3 parametr to typ zmiennej
  5. $stmt -> bindParam(':yyy', $y, PDO::PARAM_INT); // Podpinanie przez referencję, nie kopiuje zmiennej, jest szybsze
  6.  
  7. $stmt -> execute(); // Wykonujesz zapytanie
  8. (...) // I dalej już normalnie np. $stmt -> fetch(PDO::FETCH_ASSOC);
  9. ?>


Aaa, zapomniałbym - przy tym możesz zapytanie przygotować przed pętlą, a jeśli podpinasz referencyjnie to też możesz zostawić, a wykonujesz w pętli. PDO już sobie wyśle odpowiednią wartość przy referencji.
No i zamiast pobierać dane możesz nałożyć choćby unique na jakąś kolumnę, i wykonywać INSERT (...) ON DUPLICATE (...) smile.gif.
mummle
  1. <?php
  2. try {
  3. $dbk = new PDO('mysql:host=localhost;dbname=baza', 'root', 'root');
  4. $dbk->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  5. $szukaj=explode(";",$lines[0]);
  6. $odd=$szukaj[0];
  7. $issue=$szukaj[4];
  8. $issue_min=$szukaj[4]-5;
  9. $issue_max=$szukaj[4]+5;
  10.  
  11. $start=$dbk->query('create view pomocnadlon as select * from r_dane where oddzial=''.$odd.'' and nr between '.$issue_min.' and '.$issue_max.'');
  12. $search_do=$dbk->prepare('select * from pomocnadlon where id_psd= :id_psd and nr= :nr');
  13. $search_do->bindParam(':id_psd', $dane[1], PDO::PARAM_STR);
  14. $search_do->bindParam(':nr', $dane[4], PDO::PARAM_INT);
  15.  
  16. $insert_do=$dbk->prepare('INSERT INTO `r_dane` (`oddzial`, `id_psd`, `dost`, `zwr`, `nr`) VALUES (:oddzial, :id_psd, :dost, :zwr, :nr)');
  17. $insert_do->bindParam(':oddzial', $dane[0], PDO::PARAM_STR);
  18. $insert_do->bindParam(':id_psd', $dane[1], PDO::PARAM_STR);
  19. $insert_do->bindParam(':dost', $dane[2], PDO::PARAM_INT);
  20. $insert_do->bindParam(':zwr', $dane[3], PDO::PARAM_INT);
  21. $insert_do->bindParam(':nr', $dane[4], PDO::PARAM_INT);
  22.  
  23.  
  24. $update_do=$dbk->prepare('UPDATE `r_dane` set oddzial= :oddzial, id_psd= :id_psd, dost= :dost, zwr= :zwr, nr= :nr where oddzial= :oddzial and id_psd= :id_psd and nr= :nr');
  25. $update_do->bindParam(':oddzial', $dane[0], PDO::PARAM_STR);
  26. $update_do->bindParam(':id_psd', $dane[1], PDO::PARAM_STR);
  27. $update_do->bindParam(':dost', $dane[2], PDO::PARAM_INT);
  28. $update_do->bindParam(':zwr', $dane[3], PDO::PARAM_INT);
  29. $update_do->bindParam(':nr', $dane[4], PDO::PARAM_INT);
  30.  
  31. reset($lines);
  32.    foreach ($lines as $line) {
  33.            $dane=explode(";", $line);
  34.            for ($i=0;$i<=4;$i++) {
  35.                    $dane[$i]=trim($dane[$i]);
  36.             }
  37. //        $x="select * from pomocnadlon where id_psd='".$dane[1]."' and nr=".$dane[4];
  38. //        $vstart=$dbk->query($x);
  39.        $search_do->execute();
  40.        $search_do->setFetchMode(PDO::FETCH_ASSOC);
  41.        
  42.        if (empty($search_do)) {
  43. //            $insert="INSERT INTO pr_dane (oddzial, id_psd, dost, zwr, nr) VALUES ('".$dane[0]."','".$dane[1]."',".$dane[2].",".$dane[3].",".$dane[4].")";
  44. //            $insert_do=$dbk->query($insert);
  45.            $insert_do->execute();
  46.            $newr++;
  47.        } else {
  48. //            $update="update r_dane set oddzial='".$dane[0]."', id_psd='".$dane[1]."', dost=".$dane[2].", zwr=".$dane[3].", nr=".$dane[4]." where oddzial='".$dane[0]."' and id_psd='".$dane[1]."' and nr=".$dane[4];
  49. //            $update_do=$dbk->query($update);
  50.            $update_do->execute();
  51.            $updr++;
  52.        }
  53.    }
  54. $vstart=$dbk->query('drop view pomocnadlon');
  55. echo "Dodano nowe rekordy! ".$newr." <br/>";
  56. echo "Zaktualizowano rekordy! ".$updr." <br/>";
  57. }
  58. ?>

Poczytałem, zmodyfikowałem kod zgodnie z sugestiami i nadal wywala się po 300 sekundach... O co kaman? Może jest jakiś inny sposób na UPDATE?

Pozdrawiam
mummle
dr_bonzo
Dzizez

1. fopen + fgetcsv - tym sie czyta CSV'y
2. ile masz linii w tym pliku?
3. zapisuj sobie do innego pliku liczbe przetworzonych rekordow - to bedziesz wiedzial co jest nie tak i w ktorym miejscu pada
4. Nigdzie nie definiujesz zmiennej $dane przed jej uzyciem
Mephistofeles
Napisałem jeszcze, żebyś skorzystał z INSERT ON DUPLICATE KEY, poczytaj o tym, ominiesz SELECTa.
mummle
Cytat(dr_bonzo @ 16.03.2009, 11:04:27 ) *
Dzizez

1. fopen + fgetcsv - tym sie czyta CSV'y
2. ile masz linii w tym pliku?
3. zapisuj sobie do innego pliku liczbe przetworzonych rekordow - to bedziesz wiedzial co jest nie tak i w ktorym miejscu pada
4. Nigdzie nie definiujesz zmiennej $dane przed jej uzyciem

2. Do 12 tysięcy.
3. Przetwarza ok 600-800 wierszy.
4. Zmienną zdefiniowałem, mój błąd.

Cytat(Mephistofeles @ 16.03.2009, 12:33:43 ) *
Napisałem jeszcze, żebyś skorzystał z INSERT ON DUPLICATE KEY, poczytaj o tym, ominiesz SELECTa.

Problem w tym, że jednoznaczna i unikalna jest tylko para danych oddział+id. Niestety ID w oddziałach się powtarzają.

Ostatecznie, korzystając ze wszystkich rad mam coś takiego, co nadal update'uje najwyżej... 600 rekordów:
  1. <?
  2. $f=$_FILES['r_update'];
  3. $file=$f['tmp_name'];
  4. $uchwyt = fopen ($file,"r");
  5. $dane = fgetcsv($uchwyt, 100, ";");
  6. reset($dane);
  7. $newr=0;
  8. $updr=0;
  9.  
  10. try {
  11. $dbk = new PDO('mysql:host=localhost;dbname=baza', 'root', 'root');
  12. $dbk->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  13.  
  14. $odd=$dane[0];
  15. $issue=$dane[4];
  16. $issue_min=$dane[4]-5;
  17. $issue_max=$dane[4]+5;
  18.  
  19. $start=$dbk->query('create view pomocnadlon as select * from r_dane where oddzial=''.$odd.'' and nr between '.$issue_min.' and '.$issue_max.'');
  20. $search_do=$dbk->prepare('select * from pomocnadlon where id_psd= :id_psd and nr= :nr');
  21. $search_do->bindParam(':id_psd', $dane[1], PDO::PARAM_STR);
  22. $search_do->bindParam(':nr', $dane[4], PDO::PARAM_INT);
  23.  
  24. $insert_do=$dbk->prepare('INSERT INTO `r_dane` (`oddzial`, `id_psd`, `dost`, `zwr`, `nr`) VALUES (:oddzial, :id_psd, :dost, :zwr, :nr)');
  25. $insert_do->bindParam(':oddzial', $dane[0], PDO::PARAM_STR);
  26. $insert_do->bindParam(':id_psd', $dane[1], PDO::PARAM_STR);
  27. $insert_do->bindParam(':dost', $dane[2], PDO::PARAM_INT);
  28. $insert_do->bindParam(':zwr', $dane[3], PDO::PARAM_INT);
  29. $insert_do->bindParam(':nr', $dane[4], PDO::PARAM_INT);
  30.  
  31.  
  32. $update_do=$dbk->prepare('UPDATE `r_dane` set oddzial= :oddzial, id_psd= :id_psd, dost= :dost, zwr= :zwr, nr= :nr where oddzial= :oddzial and id_psd= :id_psd and nr= :nr');
  33. $update_do->bindParam(':oddzial', $dane[0], PDO::PARAM_STR);
  34. $update_do->bindParam(':id_psd', $dane[1], PDO::PARAM_STR);
  35. $update_do->bindParam(':dost', $dane[2], PDO::PARAM_INT);
  36. $update_do->bindParam(':zwr', $dane[3], PDO::PARAM_INT);
  37. $update_do->bindParam(':nr', $dane[4], PDO::PARAM_INT);
  38.  
  39. reset($dane);
  40.    while ($dane!==FALSE) {
  41.            for ($i=0;$i<=4;$i++) {
  42.                    $dane[$i]=trim($dane[$i]);
  43.             }
  44.        $search_do->execute();
  45.        $search_do->setFetchMode(PDO::FETCH_ASSOC);
  46.        
  47.        if (empty($search_do)) {
  48.            $insert_do->execute();
  49.            $newr++;
  50.        } else {
  51.            $update_do->execute();
  52.            $updr++;
  53.        }
  54.    }
  55. $start=$dbk->query('drop view pomocnadlon');
  56. echo "Dodano nowe rekordy! ".$newr." <br/>";
  57. echo "Zaktualizowano rekordy! ".$updr." <br/>";
  58. }
  59. catch (PDOException $e) {
  60.    echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();
  61. }
  62. fclose ($uchwyt);
  63. ?>

Pozdrawiam
mummle
Mephistofeles
Ale ON DUPLICATE KEY przecież sprawdza tylko unikalne klucze, więc czemu miałby nie przyjmować zwykłych pól?
mummle
Cytat(Mephistofeles @ 16.03.2009, 17:18:22 ) *
Ale ON DUPLICATE KEY przecież sprawdza tylko unikalne klucze, więc czemu miałby nie przyjmować zwykłych pól?

Z tego co zrozumiałem potrzebuję zdefiniować kolumnę jako UNIQUE. Problem w tym, iż nie mam takiej możliwości.
  1. CREATE TABLE IF NOT EXISTS `r_dane` (
  2. `oddzial` text COLLATE utf8_unicode_ci NOT NULL,
  3. `id_psd` text COLLATE utf8_unicode_ci NOT NULL,
  4. `dost` int(11) NOT NULL,
  5. `zwr` int(11) NOT NULL,
  6. `nr` int(11) NOT NULL,
  7. ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Unikatowe może być tylko połączenie pól oddzial i id_psd. Oddział pojawia się wielokrotnie i jest powtarzalny. Id_psd może być takie samo w przypadku różnych oddziałów. Fajnie i prosto byłoby gdyby tak nie było smile.gif Wiem. Jedyna opcja by skorzystać z ON DUPLICATE KEY to stworzenie "sumy" tych dwóch pól jako dodatkowe, automatycznie generowane pole w bazie, nadanie mu UNIQUE, które chyba nie przejdzie dla pola tekstowego i potem zmiana zapytania, wprowadzenie zmiennej łączącej wartości z pliku CSV i podstawienie jej za KEY. Mylę się? Tylko jak to zrobić? I czy to jest możliwe?

pzdr
mummle
Mephistofeles
A nie możesz dodać pola id generowanego z np. md5(oddzial.id_psd)? Albo nawet samo oddzial.id_psd.
mummle
Cytat(Mephistofeles @ 16.03.2009, 19:56:00 ) *
A nie możesz dodać pola id generowanego z np. md5(oddzial.id_psd)? Albo nawet samo oddzial.id_psd.

I to jest myśl, stworzyć coś jakby pole z serialem rekordu. Tylko dodałbym do tego jeszcze .nr. W ten sposób będę mógł utworzyć UNIQUE (tylko czy da się go stworzyć dla pól text lub blob?) i skorzystać z ww rozwiązania. A może przekształcić to na jakiś ciąg binarny? MD5 generuje ciągi alfanumeryczne jak dobrze pamiętam i tu może być problem z zastosowaniem indeksacji.
Potestuję, dam znać.

pzdr
mummle
Mephistofeles
Na varchary też przecież wchodzi indeks - wiele skryptów ma unique na loginie winksmiley.jpg.
mummle
Cytat(Mephistofeles @ 16.03.2009, 22:32:11 ) *
Na varchary też przecież wchodzi indeks - wiele skryptów ma unique na loginie winksmiley.jpg.

Stanęło na tym:

  1. <?php
  2. try {
  3. $dbk = new PDO('mysql:host=localhost;dbname=baza', 'root', 'root');
  4. $dbk->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  5.  
  6.    foreach ($lines as $line) {
  7.            $dane=explode(";", $line);
  8.            for ($i=0;$i<=4;$i++) {
  9.                    $dane[$i]=trim($dane[$i]);
  10.            }
  11.            $serial=$dane[0]."_".$dane[1]."_".$dane[4];
  12.            $qry="SELECT * FROM r_dane WHERE serial='".$serial."'";
  13.            $search_do=$dbk->query($qry);
  14.            $row = $search_do->fetch();
  15.            
  16.            if (empty($row['oddzial']) && empty($row['id_psd'])) {
  17.            $qry="INSERT INTO r_dane (oddzial, id_psd, dost, zwr, nr, serial) VALUES ('".$dane[0]."','".$dane[1]."',".$dane[2].",".$dane[3].",".$dane[4].",'".$serial."')";
  18.            $insert_do=$dbk->query($qry);
  19.            $newr++;
  20.            } else {
  21.            $qry="UPDATE r_dane SET dost=".$dane[2].", zwr=".$dane[3]." WHERE serial='".$serial."'";
  22.            $update_do=$dbk->query($qry);
  23.            $updr++;
  24.            }
  25.    }            
  26.  
  27. echo "Dodano nowe rekordy! ".$newr." <br/>";
  28. echo "Zaktualizowano rekordy! ".$updr." <br/>";
  29. }
  30. ?>


Śmiga aż miło. Zastanawiam się komu podziękować smile.gif

Pozdrawiam
mummle
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.