PDO update of insert
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' ?
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.
Ik maak idd gebruik van prepare() en vervolgens execute()
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()
}
//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 :
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:
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'?
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
Code (php)
Je zou hier dus een strikte vergelijking met === of !== moeten trekken, want 0 == false maar 0 !== false:
Code (php)
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}
?>
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)
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
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();
}
}
}
?>
// 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
inderdaad heb ik ook een 'wrapper' dat PDO extends.
Alleen volgens mij heeft execute() alleen true of false
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)
1
2
3
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)
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 ?
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}
}
?>
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.
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!