PDO update of insert

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Dennis WhoCares

Dennis WhoCares

21/03/2017 09:37:04
Quote Anchor link
Hi all,

ik heb in een andere topic advies gekregen om zo min mogelijk selects te gebruiken, en gewoon update tedoen.
Dat werkt prima

Maar nou op een ander project, wil ik updaten, en anders insert.

Echter... vind ik geen goede oplossing voor het volgende:
Als ik update doe van een record waar geen 'wijzigingen' plaatst vind, geeft rowCount 0
Dat is prima, maar hij geeft ook 0 bij updaten van 'niet bestaande' records.

Hoe kan ik onderscheid maken tussen 'niet bestaand' en 'geen wijziging' ?
 
PHP hulp

PHP hulp

27/11/2024 06:18:56
 
Ward van der Put
Moderator

Ward van der Put

21/03/2017 09:49:06
Quote Anchor link
rowCount() is van oudsher notoir onbetrouwbaar omdat de uitkomst afhankelijk is/was van de database-engine (InnoDB vs MyISAM), van de PDO-databasedriver en zelfs van de versie van de driver. Alléén als je prepared statements gebruikt, kun je $stmt->rowCount() gebruiken. Let er daarbij ook op dat je strikte vergelijkingen met === maakt, zodat je het verschil tussen 0 en false afvangt.
 
Dennis WhoCares

Dennis WhoCares

21/03/2017 10:19:27
Quote Anchor link
Hi Ward,

Ik maak idd gebruik van prepare() en vervolgens execute()
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$sth = $this->prepare('UPDATE ' . $table . ' SET ' . $fields . ' WHERE ' . $where);
//Bind variables
foreach($data as $key => $value) {
    if($key == 'page_content')
        $sth->bindValue(':'.$key, $value, PDO::PARAM_STR);
    else
        $sth->bindValue(':'.$key, $value);
}

if(!$sth->execute()) {
 return $this->errorInfo();
} else {
 return $sth->rowCount()
}

ik heb een 'temp' tabel aangemaakt.
met 2 records:
id field1
1 100
2 200

Als ik nou met PDO een update uitvoer als :
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
UPDATE temp SET field1 = 100 WHERE id = 1

Krijg ik geen 'error' terug maar === 0
Bij ID 2, krijg ik de eerste keer === 1 terug, en daarna weer 0

Maar met de volgende:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
UPDATE temp SET field1 = 100 WHERE id = 1000

Krijg ik geen 'error' terug maar ook weer === 0

Momenteel doe ik nog steeds eerst een 'select' op de 'id' en vervolgens een update als ik resultaat heb en anders een insert. Maar ik wil graag van de selects af.

De aantal queries gaan in zeer korte tijd explosief groeien, dus wil ik het minimaliseren.

Dus hoe kan ik nou het beste onderscheid maken tussen, 'niet bestaand' en 'geen wijziging'?
 
Adoptive Solution

Adoptive Solution

21/03/2017 10:54:29
Quote Anchor link
Als ik de vraag goed begrijp, moet een record worden bijgewerkt of, indien het niet bestaat, worden toegevoegd.

Doet me denken aan dit :

https://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html

http://stackoverflow.com/questions/12639407/sql-if-exists-update-else-insert-syntax-error
 
Ward van der Put
Moderator

Ward van der Put

21/03/2017 11:04:11
Quote Anchor link
Let om te beginnen hierop:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
<?php
$result
= 0;
if (!$result) {
    echo 'Geen resultaat'; // <-- Die krijg je hier, want 0 == false.
} else {
    echo 'Wel resultaat';
}

?>


Je zou hier dus een strikte vergelijking met === of !== moeten trekken, want 0 == false maar 0 !== false:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
if (false === $sth->execute()) {
    return $this->errorInfo();
}
else {
    return $sth->rowCount();
}

?>


Wat ik verder anders doe, is PDO exceptions laten gooien bij databasefouten. Dan komt er een PDOException uit bij fouten en die is makkelijker af te handelen dan een false:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Database extends \PDO
{
    public function __construct($dsn = null, $username = null, $password = null)
    {

        // Force lower case column names and exceptions on errors.
        $options = array(
            \PDO::ATTR_CASE => \PDO::CASE_LOWER,
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
        );

        parent::__construct($dsn, $username, $password, $options);
    }
}

?>


Verder ontkom je niet altijd aan het uitsplitsen van een UPDATE versus een INSERT. Als een rij namelijk nog niet bestaat, dan doet een UPDATE logischerwijs helemaal niets, want er is nog niets te updaten.

Ik los dat bijvoorbeeld op met een AbstractDAO (data access object) als een CRUD die een INSERT én een UPDATE kan uitvoeren. Die extendt ik vervolgens tot bijvoorbeeld een mapper of een data object met een universele methode save():

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
// CRUD: Create Read Update Delete
abstract class AbstractDAO extends Database
{
    public function create()
    {

        // INSERT
    }

    public function read()
    {

        // SELECT
    }

    public function update()
    {

        // UPDATE
    }

    public function delete()
    {

        // DELETE
    }
}


class Foo extends AbstractDAO
{
    /** @var int|null $FooID */
    private $FooID = null;

    public function save()
    {

        if ($this->FooID === null) {
            $this->create();
        }
else {
            $this->update();
        }
    }
}

?>


Als het object nog geen ID (primaire sleutel) heeft, wordt het toegevoegd en anders bijgewerkt. Zó kom ik waar jij wezen wilt: je kunt zonder er verder omkijken naar te hebben de methode save() aanroepen en die voert vervolgens achter de schermen create() uit voor een INSERT of update() voor een UPDATE.
Gewijzigd op 21/03/2017 11:05:55 door Ward van der Put
 
Dennis WhoCares

Dennis WhoCares

21/03/2017 11:37:57
Quote Anchor link
Hey Ward,

inderdaad heb ik ook een 'wrapper' dat PDO extends.
Alleen volgens mij heeft execute() alleen true of false
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
Returns TRUE on success or FALSE on failure.


In ieder geval, ik snap jouw Foo class niet helemaal :S
Hoe krijgt de class deze FooID ?

@Adpotive,

bedankt voor de links, uit de 2e link maak ik veel wijs uit.
Ik moet alleen ff m'n ID field aanpassen :) Ik hoop dat dit werkt
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
INSERT INTO component_psar (tbl_id, row_nr, col_1, col_2, col_3, col_4, col_5, col_6, unit, add_info, fsar_lock)
VALUES('2', '1', '1', '1', '1', '1', '1', '1', '1', '1', 'N')
ON DUPLICATE KEY UPDATE col_1 = VALUES(col_1), col_2 = VALUES(col_2), col_3 = VALUES(col_3), col_4 = VALUES(col_4), col_5 = VALUES(col_5), col_6 = VALUES(col_6), unit = VALUES(unit), add_info = VALUES(add_info), fsar_lock = VALUES(fsar_lock)


Alleen mis ik de 'where' in het stukje van ON DUPLICATE KEY
klopt dat ?
 
Ward van der Put
Moderator

Ward van der Put

21/03/2017 12:07:18
Quote Anchor link
Ik zal die Foo een stapje uitbreiden. Als je de create() voor de INSERT bijvoorbeeld een last insert ID of false laat retourneren, is duidelijker hoe het geheel samenwerkt:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class Foo extends AbstractDAO
{
    /** @var int|null $FooID */
    private $FooID = null;

    public function save()
    {

        if ($this->FooID === null) {
            $result = $this->create();
            if ($result !== false) {
                $this->setFooID($result);
            }
        }
else {
            $this->update();
        }
    }


    public function setFooID($foo_id)
    {

        $this->FooID = $foo_id;
    }
}

?>


Bij de eerste aanroep van save() krijgt het nieuwe object een ID die gelijk is aan de primaire sleutel, zodat in het vervolgens alleen nog een update() voor dat object wordt uitgevoerd.

Met bestaande objecten werkt die opzet ook prima: dan voer je bij het maken van het data-object eerst een read() uit die de ID direct instelt met setFooID(). Bij wijzigingen wordt daarna opnieuw alleen nog een update() uitgevoerd bij de aanroep van save(), omdat je de ID en daarmee de primaire sleutel al weet.

Bij MySQL gebruik ik de INSERT ON DUPLICATE KEY UPDATE ook veel, maar ergens past dat niet goed bij PDO: je maakt het geheel afhankelijk van een specifiek SQL-dialect. Ergens is dat natuurlijk vreemd: je gebruikt een wrapper om niet langer van één specifiek databaseplatform afhankelijk te zijn, maar voert die vervolgens met queries die uitsluitend bij specifieke databases werken. Afhankelijkheden die je aan de voordeur probeert te voorkomen, sluipen er dan via de achterdeur alsnog in.
 
Dennis WhoCares

Dennis WhoCares

21/03/2017 13:24:54
Quote Anchor link
Hi Ward,

enorm bedankt voor de verduidelijking..
Vooral de verduidelijking dat 'ON DUPLICATE KEY' specifiek SQL is :) dus laat ik deze graag achterwege.

Eerst een insert met de ID (want id's weet ik al in de import) fail => update.
(Ik zat al die tijd andersom te denken... Eerst update fail => insert)

Maar kan dus niet in 1 keer zoals de 'ON DUPLICATE KEY'
Dit heeft me enorm geholpen!
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.