Cena v různých obdobích

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

Představte si, že jsou v ceníku hotelu uloženy různé ceny pro různá období. Klient zadá datum příjezdu a odjezdu a naším úkolem je spočítat mu správnou cenu. Vypadá to jako triviální úloha, ale mírně se zesložiťuje pokud uvážíme, že doba pobytu může zasahovat do více období. Samozřejmě by se to dalo vyřešit v PHP – získali bychom ceny pro všechna období, potom postupně pro každé období spočetli překryv a vynásobili ho odpovídající cenou. Mnohem elegantnější to ale bude vyřešit přímo v SQL:

<?php
$cena = mysql_result(mysql_query("
    SELECT SUM(cena * DATEDIFF(LEAST(do_kdy + INTERVAL 1 DAY, $set[odjezd]), GREATEST(od_kdy, $set[prijezd])))
    FROM cenik
    WHERE do_kdy >= $set[prijezd] AND od_kdy < $set[odjezd]
"), 0);
?>

Začneme odzadu – podmínka je splněna pro ta období, která se překrývají s pobytem klienta, neboli ty, které platí alespoň do příjezdu klienta (vyloučí příliš staré ceníky) a zároveň platí před odjezdem (vyloučí příliš nové ceníky). Jednak tím ušetříme práci databázi a jednak tím ušetříme následnou práci sami sobě. Funkce DATEDIFF vrátí počet dní uplynulých mezi svými parametry. Od minima z odjezdu klienta a konce platnosti aktuálního ceníku odečteme maximum z příjezdu a začátku platnosti. To nám pro každé období vrátí počet dní, které se překrývá s termínem pobytu. Tady se nám hodí, že jsme použité ceníky omezili v podmínce. Pokud bychom to neudělali, vyšel by počet dní pro nepřekrývající se ceníky záporný, což bychom museli řešit např. funkcí GREATEST(0, ). Jeden den se přičítá proto, že datum pobytu je zvykem uvádět včetně hraničních dnů (např. od pátku do neděle), ale ceníky se nepřekrývají (pokud jeden platí do pátku a druhý od soboty, tak klient zaplatí za jednu noc z každého z nich).

Jakub Vrána, Řešení problému, 6.2.2006, diskuse: 6 (nové: 0)

Diskuse

Jan Menšík:

Asi by bylo dobre nejdrive ukazat jak vypada SQL tabulka. Ceny pro pokoje a dny se daji ulozit mnoha zpusoby a podle toho vypracovat optimalizovany SQL dotaz. Tohle je naprosto vytrzene z kontextu.

ikona Jakub Vrána OpenID:

Dokážu si představit jedinou strukturu tabulky, která dotazu vyhovuje: cenik (od_kdy date, do_kdy date, cena int). Proto jsem ani nepovažoval za důležité ji uvádět, z dotazu se dá snadno poznat.

Pokud by období byla uložena zvlášť (high season, low season), tak by v dotazu přibylo jedno spojení tabulek, ale myšlenka, kterou jsem se pokusil článkem sdělit, by zůstala stejná.

Andrew:

Tak vytržené z kontextu to jistě je a u příkladů SQL dotazů je zvykem uvést tabulky, nad kterými je dotaz prováděn (souvislosti občas Jakubovi v článcích prostě chybí), nicméně si myslím, že není zas tak složité použít trochu hlavu a každý zjistí, že definice tabulky zas tak nutná není, jelikož je jednoduchá. A mimo to, Jakub přece píše, že je to blog pro "mírně pokročilé" a těm se snad nemusí vše servírovat až pod nos. Tady totiž nejde o kuchařku, ale ukázku zajímavé myšlenky. A pro ty ostatní zkusím stručně tabulku - kdyžtak mě opravte:

CENIK
...
cena
od_kdy
do_kdy
...

cena - částka pro dané období
od_kdy - začátek daného období
do_kdy - konec daného období
... - libovolný balast

Jinak bezva nápad - stále ještě věcí jak vidím dělám složitě.

Andrew:

No nic, to je tak, když někdo píše pomalu. Než jsem se stihnul vypovídat, tak mě jakub mnohem stručněji bředběh. :-)

Petr Tvaroha:

SELECT SUM(cena * DATEDIFF(LEAST(do_kdy + INTERVAL 1 DAY, $set[odjezd]), GREATEST(od_kdy, $set[prijezd]))) AS cena,
SUM(DATEDIFF(LEAST(do_kdy + INTERVAL 1 DAY, $set[odjezd]), GREATEST(od_kdy, $set[prijezd]))) AS pocet_dni
    FROM cenik
    WHERE do_kdy >= $set[prijezd] AND od_kdy < $set[odjezd]
    HAVING pocet_dni = DATEDIFF($set[odjezd],$set[prijezd])

A tímto ještě ošetříme případnou chybu se špatně zadanými termíny cen (když např. chybí jedna sezona a host by tím získal velikou slevu:).

Jinak skvěle jednoduchý dotaz, Jakube, díky za něj a za ostaní články!

Miloš Brecher:

V praxi bude chtít klient vidět transparentní účet - detailní rozpis dní, ceny za den a součet. Protože když bude celková cena výsledkem sumarizace z více ceníků, nebude se v tom orientovat. A tabulka reálného ceníku bude složitější - nejspíš dvě tabulky, v jedné date_od, date_do, ve druhé ceny za různé pokoje, dospělý, dítě, podle počtu nocí, příplatek za psa, polopenze, apod...

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 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.