Generování čísla objednávky
- BayerMeister
- Člen | 10
Zdar!
Jak by se s Nette/Database(/Table) dal vložit nový záznam s ID, který
vychází z posledního předchozího záznamu?
Chtěl bych totiž číslovat objednávky takto: (int) YY####, kde YY je 17 pro
tento rok.
S čistým MySQL bych to udělal na 2 dotazy s pomocnou proměnnou,
uzavřené do transakce. Takto:
start transaction;
select @nextId := if(isnull(max(id)), 170000, max(id)) + 1 from objednavka where id > 170000 && id < 180000;
insert into objednavka (id, ...další sloupce) values(@nextId, ...další hodnoty);
commit;
A následně bych to ještě přes @nextId selectnul.
Zkusil jsem to pomocí context->query(…). Sice to vloží, ale je to
špinavý (nejen MySQL specific) a po transakci se mi nedaří získat poslední
vložené ID. Zde je kód:
$sql = "start transaction;
select @nextId := if(isnull(max(id)), 170000, max(id)) + 1 from objednavka where id > 170000 && id < 180000;
insert into objednavka (id, data) values(@nextId, 1234);
commit;";
$result = $this->database->query($sql);
$id = $this->database->getInsertId('objednavka');
\Tracy\Debugger::barDump($result);
\Tracy\Debugger::barDump($id);
V $resultu je ResultSet [… result ⇒ NULL, resultKey => –1, results ⇒ NULL …], v $id je „0“
Vůbec se nechci držet tohoto postupu. Důležitý je jen cíl: mít klíče
v uvedeném tvaru.
Přemýšlel jsem i o tom, že bych měl PK normální AUTO_INCREMENT, ale
mechanizmus který by objednávkám pak přiděloval hezké číslo do jiného
unique sloupce by vyžadoval stejnou atomičnost.
Možnost ošklivá je risknout vložit bez synchronizace a chytat nějakou
SQL Exception. Dokud lítá Exception, úsilí opakovat (tj. znov zjistit
nejvyšší dosavadní klíč…).
To je ale asi tak hezký, jako psát v Nette čisté MySQL ručně.
Předem díky! Richard
P.S.: to je mi jasný, že to někde na fóru už bude, ale… nenašel jsem to :-)
- BayerMeister
- Člen | 10
Lidi díky za reakce. Nějak mi nedošla žádná notifikace na mail…
Nastavovat auto_incremet z jiného skriptu, který se spustí na CRON (nikdy 2× současně), by mělo nevýhodu, že se Vážu na hosting dalším nastavením (ale s tím se lze smířit).
Bohužel jsem zatajil další drobnost, která se v tomto návrhu ukazuje
podstatnou:
Formát by obsahoval další dvojčíslí závislé na jiné okolnosti,
řekněme, předmětu nákupu: YYXX####, takže by paralelně vznikaly
objednávky 1701#### a 1702####
Po nějakou dobu bude ale existovat pouze předmět „01“, takže lze
dočasně postup s CRONem použít.
Takže dočasně dobrý, ale chtělo by to nějaký best-practice na tenhle typ atomických getmax-increment-insert-select.
- BayerMeister
- Člen | 10
Nechápal a tak testoval jsem, jak se vlastně ->query() chová, když se
jí podá více příkazů oddělených středníkem.
Třeba 2 selecty: do DB se odešlou oba, návratová hodnota ale zohledňuje
první dotaz a ne poslední, jak jsem čekal.
No a proměnné @MySQL mají platnost pro úřipojení… tak mi došlo, že to celé, co tady prasím do jednoho příkazu, je zbytečné, že ta proměnná mi přežije i do dalšího volání ->query()
A tak mám:
$this->database->query('start transaction');
$this->database->query('select @next_id := if(isnull(max(id)), 170000, max(id)) + 1 from objednavka where id > 170000 && id < 180000');
$this->database->query('insert into objednavka (id, data) values(@next_id, 1234)');
$this->database->query('commit');
$result = $this->database->query('select @next_id as next_id');
…a $result vrací, co jsem potřeboval – vložené ID.
Další rozšíření o různé řady objednávek je jednoduché – mění se konstanty 170000/180000 (dno/strop) na 17010000/17020000.
Pořád prasárna, ale už jsme blíž :-)