REPLACE

Školení, která pořádám

MySQL obsahuje nestandardní SQL příkaz REPLACE, který dovoluje vložit do tabulky data nebo je přepsat v případě, že koliduje primární klíč nebo jiný unikátní index. S využitím tohoto příkazu je možné velice snadno ošetřit obsluhu společného formuláře pro editaci a vložení záznamu:

<?php
// tradiční řešení
if (isset($_GET["select"])) {
    mysql_query("UPDATE tabulka SET ... WHERE id = " . intval($_GET["select"]));
} else {
    mysql_query("INSERT INTO tabulka (...) VALUES (...)");
}

// s využitím REPLACE
mysql_query("REPLACE tabulka SET id = " . intval($_GET["select"]) . ", ...");
?>

Příkaz REPLACE jsem dřív používal, pak jsem s tím ale kvůli jeho nestandardnosti přestal (byť např. SQLite ho také podporuje).

Příkaz má ještě jednu nepříjemnou vlastnost – v případě kolize smaže všechny kolidující řádky a vloží místo nich ten nový. Takže pokud např. v tabulce uzivatele (id int NOT NULL AUTO_INCREMENT, login varchar(30) NOT NULL, UNIQUE (login), PRIMARY KEY (id)) máme řádky (1, 'vasek'), (2, 'petr') a provedeme REPLACE uzivatele SET id = 2, login = 'vasek', zbude v tabulce jediný řádek (2, 'vasek'). Rozumnější by podle mě bylo, kdyby REPLACE detekoval kolize pouze u primárních klíčů.

Pokud mě paměť neklame, tak v minulosti bylo možné využívat hodnoty zaměňovaného řádku, takže např. aktualizace počítadla čtenosti článku v jednotlivých dnech byla jednoduchá (předpokládá se existence unikátního indexu nad sloupci clanek, datum):

<?php
// tradiční řešení
mysql_query("INSERT INTO ctenost (clanek, datum, pocet) VALUES (" . intval($_GET["id"]) . ", CURDATE(), 1)");
if (!mysql_affected_rows()) {
    mysql_query("UPDATE ctenost SET pocet = pocet + 1 WHERE clanek = " . intval($_GET["id"]) . " AND datum = CURDATE()");
}

// s využitím REPLACE ve starších verzích MySQL
mysql_query("REPLACE ctenost SET clanek = " . intval($_GET["id"]) . ", datum = CURDATE(), pocet = pocet + 1");
?>

Tato vlastnost ale byla označena za chybu a byla odstraněna. Přípomínám, že opačný postup u tradičního řešení (INSERT až po vyzkoušení UPDATE) by mohl vést k neuložení záznamu a testování pomocí SELECT by vedlo k nutnosti zamknout tabulku.

Podtrženo, sečteno – nejen kvůli nestandardnosti příkazu, ale hlavně kvůli problémům spojeným s unikátními indexy příkaz používat nedoporučuji. Málokdo asi stojí o to, aby kvůli přidanému unikátnímu indexu začala z tabulky mizet data.

V MySQL 4.1 byl představen modifikátor ON DUPLICATE KEY UPDATE příkazu INSERT, který je příkazu REPLACE podobný, ale nemá tak destruktivní účinky. Přesně tento příkaz se hodí pro aktualizaci čtenosti:

<?php
// s využitím ON DUPLICATE KEY UPDATE od MySQL 4.1
mysql_query("INSERT INTO ctenost (clanek, datum, pocet) VALUES (" . intval($_GET["id"]) . ", CURDATE(), 1) ON DUPLICATE KEY UPDATE pocet = pocet + 1");

// získání hodnoty sloupce id s příznakem AUTO_INCREMENT
$id = (mysql_affected_rows() == 1 ? mysql_insert_id() : mysql_result(mysql_query("SELECT id FROM ctenost WHERE clanek = " . intval($_GET["id"]) . " AND datum = CURDATE()"), 0));
?>

Pokud ke konfliktu unikátního klíče nedojde, vrátí mysql_affected_rows jedničku, jinak dvojku.

Přijďte si o tomto tématu popovídat na školení Návrh a používání MySQL databáze.

Jakub Vrána, Výuka, 23.9.2005, diskuse: 10 (nové: 0)

Diskuse

ikona dgx:

Jakube, pokud nastavíš UNIQUE (login), tak očekáváš, že by v tabulce měly být dva řádky s login='vasek'?

ikona llook:

To asi ne, ale já bych očekával, že to vyhodí chybu. Pokud se někdo snaží uživateli id=2 dát obsazené jméno, nečekal bych, že to provede a původního nositele jména smaže.

Lépe to řeší metoda Replace() z ADOdb. Dostane název tabulky, hodnoty a název klíče. Pokud se jí nepodaří UPDATE, tak zkusí INSERT. Záleží jenom na mě, co určím jako klíč.

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL: Reakce na: llook

Radim Smička:

Tady někdo nesnáší Operu. Na weblogu bych to opravdu nečekal :(.

Pokud se na stránku podívám Operou vyhodí to vždy
Connection closed by remote server

Rado:

Mi Opera bez problémů funguje.

endlife:

problém je asi na Vašem příjimači, chodím sem s Operou od zrodu této subdomény a bez problému..

i když si moc nedokážu představit, jak se dá tohoto stavu docílit..

salko:

Pripájam sa, chodím sem výhradne s Operou a vždy na 100% v pohode. Aku máte Operu? OS? FW? Vírus?

Gimli2:

Taktez opera naprosto v poradku. (8.02)

cita:

opera 8.5 a v poho

koki:

taktéž bezproblémově s operou 8.0 ;-)

SQi:

JJ, Opera jede ok
avatar © 2005-2020 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.