Autor Zpráva
Ottik
Profil *
Ahoj, když něco vypisuju s fetch
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) {
}
tak při posledním kroku (potom co se do obj uloží poslední nalezený výsledek) už nemám přístup k poslednímu, Dá se nějak vrátit o krok zpět?
RastyAmateur
Profil
Ottik:
Nevím o tom. Každopádně můžeš to vyřešit i jinak. Například uložit si všechna data do pole např. pomocí fetchAll(), nebo jestli ti jde jen o ten předposlední, můžeš si tam vytvořit nějakou pomocnou proměnnou
Ottik
Profil *
vyřešil jsem to prozatím tím fetchall, jenom mě zajímalo jestli se dá posunovat i zpět. Fetch, je podle mě lepší na zápis, ale kvůli tomu poslednímu výsledku používám fetchall
Joker
Profil
Pokud vím, čtení záznamů z databáze funguje jen jednosměrně, není možné záznam „odnačíst“ a vrátit se k předchozímu.

Ale jestli potřebujete i ten minulý záznam, není problém si ho uložit do proměnné:
$prev = null;
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) { 
/* zpracování, v $prev je předchozí záznam nebo null pokud $obj je první záznam */
$prev = $obj;
} 
Keeehi
Profil
Jedna možnost je, jak už psal RastyAmateur, si na konci smyčky ukládat aktuální objekt, takže v příštím průběhu půjde vlastně o předposlední objekt.
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) { 

    ...
    
    ...

    $lastObj = $obj;
} 

Druhou možností je říct funkci fetch, že nechceš další záznam ale předchozí. Je ale na začátku potřeba specifikovat cursor, protože ten defaultní je jen dopředný.

$stmt = $db->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
...
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) { 
    ...
    if ( /* chci zjistit předchozí záznam */) {
        $prevObj = $stmt->fetch(PDO::FETCH_OBJ, PDO::FETCH_ORI_PRIOR);
    }
    ...
} 

Případně se dá použít PDO::FETCH_ORI_REL s hodnotou -1. Nebo nějaká jiné vhodná konstanta.
N71
Profil *
A má smysl to takto komplikovat? Stejně procházíš všechny výsledky. Pokud se nejedná o nějaké enormní množství dat, které bys nechtěl mít najednou v paměti, tak bych to fetchnul všechno najednou pomocí fetchAll.
Ottik
Profil *
Joker:
není problém si ho uložit do proměnné:
tohle mě napadlo taky, ale nelíbí se mi že bych to postupně ukládal abych získal poslední.

Keeehi:
Je ale na začátku potřeba specifikovat cursor, protože ten defaultní je jen dopředný.
hm, to by už šlo, ale jak mám napsat tu hodnotu? Nic se mi nevypíše
$stmt = $db->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
...
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) { 
...
} 
$prevObj = $stmt->fetch(PDO::FETCH_OBJ, PDO::FETCH_ORI_REL -1);
echo $prevObj; // nic se nevypíše



N71:
Pokud se nejedná o nějaké enormní množství dat, které bys nechtěl mít najednou v paměti, tak bych to fetchnul všechno najednou pomocí fetchAll.
pokud vím tak defaultně se to ukládá do paměti ať už použiju fetch nebo fetchAll, nebo se pletu?
N71
Profil *
Ottik:
pokud vím tak defaultně se to ukládá do paměti ať už použiju fetch nebo fetchAll, nebo se pletu?
V cyklu držíš v proměnné $obj vždy jen jeden výsledek najednou. Pokud by šlo o desítky megabajtů dat, tak by to mohlo mít svůj smysl.
Ale běžně je to jedno – cyklus má tady většinou smysl použít, jen když ho plánuješ někde přerušit a zbytek přeskočit.
Keeehi
Profil
Ottik:
tohle mě napadlo taky, ale nelíbí se mi že bych to postupně ukládal abych získal poslední.
Je to velmi jednoduché řešení a hlavně k ukládání nedochází. Do proměnné se vlastně jen uloží kde v paměti je objekt uložen. Nemusí se tedy vůbec nikam kopírovat/přesunovat. Je to velmi nenáročná a rychlá operace, takže vubec ničemu nevadí.

$prevObj = $stmt->fetch(PDO::FETCH_OBJ, PDO::FETCH_ORI_REL -1)
Do dokumentace jsi se nepodíval, že? -1 je třetí parametr, nemáš to odečítat od té konstanty. (Chybí ti před ní čárka)
Nevím jak se chová iterátor když dojde až za poslední záznam. Ale snad by se měl umět vrátit.

pokud vím tak defaultně se to ukládá do paměti ať už použiju fetch nebo fetchAll, nebo se pletu?
Do paměti se to ukládá, ovšem při fetch je to jen jeden řádek, při fetchAll celý vysledek.

Ještě je tu možnost, jelikož se snažíš trefit konec pole, tak můžeš si počítat na kolikátém záznamu jsi a porovnávat s rowCount. Což tedy dle dokumentace nemusí fungovat se všemi databázemi ale předpokládám že s MySQL nebude problém.
RastyAmateur
Profil
Ottik:
tohle mě napadlo taky, ale nelíbí se mi že bych to postupně ukládal abych získal poslední.
Jen tak pro jistotu, o co vlastně jde? Nejde to třeba vyřešit už v tom SQL dotazu? Já jen jestli se zbytečně nevybírají všechny možné výsledky, aby jsi dostal jeden, předposlední...
ottik
Profil *
Keeehi:
Je to velmi jednoduché řešení a hlavně k ukládání nedochází. Do proměnné se vlastně jen uloží kde v paměti je objekt uložen.
Nechci zpochybňovat tvoje znalosti, ale kdyby to tak bylo a já bych změnil $prev muselo by se tím pádem změnit i $obj, možná kdybych při tom přiřazování použil ampersand &$obj tak by se tam jenom ta adresa použila... Aspoň takhle by to bylo v C++.

Do dokumentace jsi se nepodíval, že? -1 je třetí parametr, nemáš to odečítat od té konstanty. (Chybí ti před ní čárka)
Ano já vím, zkoušel jsem objevil možnosti a když to nefungovalo jako třetí parametr tak jsem to zkusil bez té čárky a pak jsem to sem zkopíroval. Asi jak to přešlo přez konec tak už se nedá vrátit.

Do paměti se to ukládá, ovšem při fetch je to jen jeden řádek, při fetchAll celý vysledek.
Opravdu? Mám za to že potom execute se výsledek uloží celý do paměti a přitom whyle cyklu pak do té proměnné postupně kopíruje z paměti popřípadě všechno z paměti do proměnné při použití fetchAll. Kdybych v tom cyklu whyle vynulovat $db = NULL připojení k databázi tak k těm datům mám pořád přístup. A nebo sem to špatně pochopil, ale mám za to že se tohle dá ovlivnit nastavením.

RastyAmateur:
Jen tak pro jistotu, o co vlastně jde? Nejde to třeba vyřešit už v tom SQL dotazu?
Vracím JSON s HTML kódem a součástí toho i poslední ID výsledku.
Keeehi
Profil
ottik:
Aspoň takhle by to bylo v C++
Ano, reference v C/C++ fungují jinak než v PHP. Co já jsem si užil na škole ukazatelů na ukazatele :) A těch chyb a problémů s pamětí. PHP naštěstí programátora od toho velmi šikovně odstíní. Jestli tě to zajímá, tak tady máš přímo vysvětlení počítaných referencí. php.net/manual/en/features.gc.refcounting-basics.php

Mám za to že potom execute se výsledek uloží celý do paměti a přitom whyle cyklu pak do té proměnné postupně kopíruje z paměti popřípadě všechno z paměti do proměnné při použití fetchAll.
Je jasné, že všechna ta data někde být v paměti musí. Jestli jsou všechna přenesena do PHP nebo jsou v paměti databáze a PHP si je postupně dotahuje to nevím. Nicméně při použití fetch se udělá kopie jen jednoho řádku. A pokud se to provádí do stále stejné proměnné, tak počet řádků v paměti je N z dotazu +1 v proměnné. Pokud se použije fetchAll, tak počet řádků v paměti je N z dotazu +N v proměnné ve které je to pole.

Vracím JSON s HTML kódem a součástí toho i poslední ID výsledku.
V tom případě bych to vůbec nekomplikoval. Viděl bych to na něco v tomto stylu.
while ($obj = $stmt->fetch(PDO::FETCH_OBJ)) {
    $r = [];
    $r['id'] = $obj->id;
    $r['name'] = $obj->name;
    $r['timestamp'] = $obj->created;
    
    $data[] = $r;
    $lastID = $obj->id;
}

echo json_encode(['data' => $data, 'last_id' => $lastID]);
Joker
Profil
ottik:
Nechci zpochybňovat tvoje znalosti, ale kdyby to tak bylo a já bych změnil $prev muselo by se tím pádem změnit i $obj, možná kdybych při tom přiřazování použil ampersand &$obj tak by se tam jenom ta adresa použila... Aspoň takhle by to bylo v C++.

Je to jak popisuje Keeehi.
Stručně řečeno v PHP to přiřazení $prev = $obj; interně neudělá novou kopii $obj, ale jen odkaz (referenci) na $obj.

To trvá dokud jsou jejich hodnoty stejné; Změna některé proměnné rozpojí tu referenci. Což řeší ten zmíněný problém provázání hodnot.
TomášK.
Profil *
Keeehi
„Mám za to že potom execute se výsledek uloží celý do paměti a přitom whyle cyklu pak do té proměnné postupně kopíruje z paměti popřípadě všechno z paměti do proměnné při použití fetchAll.“
Je jasné, že všechna ta data někde být v paměti musí.

Určitě? Myslím si, že má databáze uložený pointer do tabulky a buffer třeba pro 1000 záznamů. Při čtení sto milionu záznamů tak nikde v paměti není víc než ten tisíc, php si to postupně tahá.
ottik
Profil *
TomášK.:
Určitě? Myslím si, že má databáze uložený pointer do tabulky a buffer třeba pro 1000 záznamů.
A co když se rozhodnu vymazat dvoutisicatouprvoupolozku během toho co to PHP bude vytahovat? To bude databáze čekat než si to PHP dotahá a pak to smaže a nebo to smaže a php to už nedostane?
TomášK.
Profil *
ottik:
Myslím, že ve většině případů bude databáze čekat, než si to PHP dotahá. Z pohledu databáze jsou to dvě transakce, které čtou/zapisují stejná data, může se to lišit podle nastavení izolace transakcí. Ale musel bych to ověřit. Je možné, že databáze usoudí, že SELECT data ještě neviděl, tudíž je korektní udělat DELETE, bude to odpovídat rozvrhu, ve kterém se provede T(delete) před T(select). Nebo možná bude držet dvě oddělené verze dat a každou bude předkládat jiné transakci (šlo by to udělat třeba tak, že se záznam nesmaže, ale označí, že je ke smazání s časovým razítkem a pak se kontroluje, jestli ten SELECT začal před nebo pod smazání; Reálné smazání se provede, až nebudou existovat transakce, které začaly před smazáním).
Joker
Profil
TomášK.:
Myslím si, že má databáze uložený pointer do tabulky a buffer třeba pro 1000 záznamů. Při čtení sto milionu záznamů tak nikde v paměti není víc než ten tisíc, php si to postupně tahá.

To se mi nezdá, je ten názor něčím podložený?
Jednak s touhle metodikou by před zpracováním všech řádků¹ nešlo zjistit kolik jich celkem ve výsledku je, což rozhraní na databáze často umožňují.
Ale hlavně výsledek dotazu musí odpovídat stavu databáze ve chvíli jeho provedení. A jelikož obsah databáze se může kdykoliv změnit, musí někde být kompletní data výsledku².

Myslím, že ve většině případů bude databáze čekat, než si to PHP dotahá.
Ani tohle se mi nezdá.
Zpracování výsledku trvá řádově déle než provedení SQL dotazu, není celkem problém napsat kód, který poběží třeba hodinu, a celou tu dobu by databáze byla blokovaná.

Podle mě se tohle může lišit podle typu databáze, nastavení a použitém rozhraní pro přístup.
Ale aby to nebyly jen spekulace:
V manuálu jsem přesný popis fungování nenašel, ale z tohohle dotazu vyplývá, že se skutečně přenese do paměti celý výsledek.

A ještě trocha spekulací, asi nejsnazší postup by byl z výsledku dotazu udělat nějakou dočasnou tabulku a v případě potřeby odložit na disk.

¹ Resp. teoreticky všech mínus velikost bufferu.
² I když teoreticky pokud by databáze podporovala např. žurnálování nebo jinak držela celou historii dat, teoreticky by šlo udělat odkaz na stav dat ve chvíli provedení SELECT dotazu. Ale to je asi jen teorie.
TomášK.
Profil *
Joker:
To se mi nezdá, je ten názor něčím podložený?
Očekávám, že dokážu zpracovat i výsledek dotazu, který se mi nevejde do paměti. A nenapadá mě, proč bych nebyla možnost iterovat i zpět, skákat na libovolný záznam, pokud by bylo všechno v paměti.

Jednak s touhle metodikou by před zpracováním všech řádků¹ nešlo zjistit kolik jich celkem ve výsledku je, což rozhraní na databáze často umožňují.
Na to nemusí být data v paměti, stačí přes ně provést cyklus, nejde-li to lépe (z indexu).

Ale hlavně výsledek dotazu musí odpovídat stavu databáze ve chvíli jeho provedení. A jelikož obsah databáze se může kdykoliv změnit, musí někde být kompletní data výsledku.
To myslím databáze negarantuje. Databáze musí uspořádat transakce a vracet výsledky tak, aby to bylo ekvivalentní nějakému sériovému vykonávání, s tím, že podle nastavení izolace transakcí může dojít k nějakým anomáliím. Myslím, že může DELETE, který příjde po SELECTu, vykonat před ním, pokud ten SELECT ještě nepoužil daná data. Tipuju, že se ukládání výsledku dotazu bude lišit podle toho, jestli je dotaz přímo na tabulku, nebo jestli je dotaz složitější a je potřeba resultset uložit. Typicky asi výsledek dotazu bude mít databáze v paměti, pokud jí na to stačí cache, jinak ho zapíše na disk.


Myslím, že ve většině případů bude databáze čekat, než si to PHP dotahá.“
Ani tohle se mi nezdá.
Co když v rámci SELECT-transakce zopakuju dotaz na stejná data? V tu chvíli má databáze problém, měla by vrátit stejný výsledek, ale mezitím DELETE něco smazal. Buď tedy nepovolí DELETE, nebo udržuje historii dat pro SELECT-transakci, nebo povolí non-repeatable read.
Experiment ukázal, že se to chová s různými nastaveními různě. Ve výchozím nastavení DELETE projde, i pokud zrovna tahám data. Pokud zruším AUTO-COMMIT a nastavím nejpřísnější izolaci, čeká DELETE než dotahám výsledky SELECTu (resp. do COMMITU), klidně několik minut. Za poznámku taky stojí, že nemůžu poslat COMMIT, dokud neuzavřu result pro SELECT, při auto-commitu se zdá, že COMMIT proběhne hned po selectu, ne až při uzavření resultsetu.


Podle mě se tohle může lišit podle typu databáze, nastavení a použitém rozhraní pro přístup.
Souhlas. V manuálu jsem našel, že nemám pravdu, php ve výchozím nastavení tahá do paměti všechno.

I když teoreticky pokud by databáze podporovala např. žurnálování nebo jinak držela celou historii dat, teoreticky by šlo udělat odkaz na stav dat ve chvíli provedení SELECT dotazu. Ale to je asi jen teorie.
To není jen teorie, viz en.wikipedia.org/wiki/Multiversion_concurrency_control. Pokud provedu DELETE, tak databáze musí tak jako tak držet až do COMMITU a skutečného zapsání na disk původní verzi dat, tak proč to nepoužít i při transakcích.
Ottik
Profil *
TomášK.:
Očekávám, že dokážu zpracovat i výsledek dotazu, který se mi nevejde do paměti. A nenapadá mě, proč bych nebyla možnost iterovat i zpět, skákat na libovolný záznam, pokud by bylo všechno v paměti.
otázkou je jak, protože tamto keehi mi nešlo potom co to přešlo přez konec.
Joker
Profil
TomášK.:
Očekávám, že dokážu zpracovat i výsledek dotazu, který se mi nevejde do paměti.
Pro takový předpoklad nevidím důvod.
Nicméně to asi reálně není problém, jeden záznam mívá většinou řádově jednotky až stovky bajtů, takže i desítky milionů je pár GB, to není nic, co by běžný databázový server nedokázal uložit.

To není jen teorie
Samozřejmě, ostatně třeba systémy pro správu verzí (jako Git) jsou taky svým způsobem databáze a tohle umějí.
Ale u běžné relační databáze by to jednak ten SELECT zbytečně zpomalovalo a jednak by to sledování změn zabíralo spoustu místa.
TomášK.
Profil *
Joker:
Pro takový předpoklad nevidím důvod.
Podobně to vnímám u čtení souboru. Očekávám, že budu mít možnost číst soubor řádek po řádku (resp. to nějakém bloku) bez toho, abych celý soubor natáhl do paměti. Z principu to tak být nemusí, ale mají-li se věci dělat efektivně, je to potřeba.

Ale u běžné relační databáze by to jednak ten SELECT zbytečně zpomalovalo a jednak by to sledování změn zabíralo spoustu místa.
Pokud to umožní zpracovávat víc transakcí najednou, tak "cena" za zpomalení je akceptovatelná. Z odkazované wikipedia jsem nabyl dojmu, že Multiversion concurrency control se používá ve velkém, na seznamu databází, které to používají, je MySQL, PostgreSQL, Oracle, Microsoft SQL server, MariaDB, MongoDB, ….
Keeehi
Profil
otázkou je jak, protože tamto keehi mi nešlo potom co to přešlo přez konec.
No je to kvůli tomu, že PDO nepodporuje scrollable cursor pro MySQL. bugs.php.net/bug.php?id=34625
Naproti tomu rozšíření mysqli (i staré mysql) skákání v datech podporovaly (mysqli_data_seek).
Bohužel se z toho takto napřímo nedá poznat, zda je problém u PDO které jen něco nemá implementováno i když to existuje, nebo je problém už u samotné MySQL která se neumí vracet a funkce mysqli_data_seek to obcházela tento nedostatek vlastní implementací v paměti. To by se muselo hledat v kódu a to se mi opravdu nechce. Malá nápověda by mohlo být, že v MySQL funkce mysql_data_seek existuje, takže podle toho to spíše vypadá, že to někdo nenaimplementoval jen do PDO. Tato funkce se objevuje v dokumentaci MySQL minimálně z roku 2000 takže to není žádná novinka kterou zatím nestihli zapracovat. Na druhou stranu se v tom bugreportu píše že MySQL nepodporuje cursory, takž to implementovat nejde. Tak já nevím.

Vaše odpověď

Mohlo by se hodit


Prosím používejte diakritiku a interpunkci.

Ochrana proti spamu. Napište prosím číslo dvě-sta čtyřicet-sedm:

0