tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

Jde v DibiFluent skladat SQL prikaz aneb efektivita dotazu?

před 8 lety

ras
Člen | 127

Ahoj vsem,

snad je nadpis odpovidajici tomu na co chci zeptat ;o)

Resim aktualizaci poradi pro zobrazeni, tj. mam v MySQL tabulku napr. XXX se sloupci primary_index(id), unique(poradi) a text.
Mam tam rekneme 1000 radku (zatim je poradi shodne s id) a ted mi uzivatel skrze fomular rekne, ze id=999 majici do ted poradi 999 bude mit nove poradi 10 → tj. potrebuji vygenerovat dotaz/dotazy na zmenu id v intervalu 10 az 999.

Jak to nejlepe udelat?

1.) vytvorit pro kazde ID update->execute() → to je asi trochu .. ehm … nestastne reseni

2.) vygenerovat pro kazde ID update, ale pouze je kumulovat a pak naraz provest ->execute() → to je asi vhodnejsi, ale napada Vas nekoho jako to udelat pres DibiFluent ?

3.) existuje nejaka lepsi treti cesta?

Diky za info.

Radek

Editoval ras (23. 11. 2011 22:29)

před 8 lety

Milo
Nette Core | 1119

Existuje několik dalších cest. Vše musí proběhnout uzavřené v transakci, zkráceně zapsáno takto:

a) Dočasně odstranit unikátní klíč

  • odstranit unikátní klíč
  • updaty pomocí poradi = poradi + 1 atd.
  • nahodit unikátní klíč

b) Druhým sloupcem a povolením NULL ve sloupci poradi

  • povolíš NULL ve sloupci pořadí
  • vytvoříš nový sloupec poradi_new
  • UPDATE poradi_new = poradi
  • srovnáš si jak potřebuješ sloupec poradi_new
  • UPDATE poradi = NULL
  • UPDATE poradi = poradi_new

c) Zvětšením pořadí nad aktuální maximum

  • $max = SELECT MAX(poradi)
  • updaty pomocí poradi = poradi + $max + 1 WHERE ... atd.

Vše má svoje výhody i nevýhody, záleží i na počtu řádků v tabulce. Já používám variantu c)

před 8 lety

paranoiq
Člen | 388

@ras: a na základě čeho má být pořadí změněno? nepřijde mi nijak užitečné určovat ad hoc pořadí u 1000 záznamů. pokud se pořadí řídí podle nějakého parametru záznamu, možná by bylo lepší využít toho

před 8 lety

Milo
Nette Core | 1119

Doma jsem našel kus kódu k řešení c)

$old = 999;
$new = 10;

try{
    $db->begin();
    $limits = $db->query('SELECT MAX(poradi) AS max, MIN(poradi) AS min FROM tab')->fetch();

    $k = $limits->max - $limits->min + 1;

    $old = min(max($old, $limits->min), $limits->max);
    $new = min(max($new, $limits->min), $limits->max);

    if ($old < $new) {
        $db->query('UPDATE tab SET poradi = poradi + %i', $k, 'WHERE poradi > %i', $old, 'AND poradi <= %i', $new);
        $db->query('UPDATE tab SET poradi = %i', $new, 'WHERE poradi = %i', $old);
        $db->query('UPDATE tab SET poradi = poradi - %i - 1', $k, 'WHERE poradi > %i', ($old + $k), 'AND poradi <= %i', ($new + $k));

    } else {
        $db->query('UPDATE tab SET poradi = poradi + %i', $k, 'WHERE poradi < %i', $old, 'AND poradi >= %i', $new);
        $db->query('UPDATE tab SET poradi = %i', $new, 'WHERE poradi = %i', $old);
        $db->query('UPDATE tab SET poradi = poradi - %i + 1', $k, 'WHERE poradi < %i', ($old + $k), 'AND poradi >= %i', ($new + $k));
    }
    $db->commit();

} catch (Exception $e) {
    $db->rollback();
    throw $e;
}

před 8 lety

ras
Člen | 127

Milo: Diky moc za zpusoby reseni … je to sice neco uplne jineho nez co jsem mel v planu, ale proc ne.

Kazdopadne muj puvodni plan bylo to provest nejak takto

$sql = "UPDATE XXX SET 'poradi' = 0 WHERE 'poradi' = 999";
<nejaky cyklus v PHP>
$sql .= "UPDATE XXX SET 'poradi' = 999 WHERE 'poradi' = 998";
$sql .= "UPDATE XXX SET 'poradi' = 998 WHERE 'poradi' = 997";
.
.
.
$sql .= "UPDATE XXX SET 'poradi' = 12 WHERE 'poradi' = 11";
$sql .= "UPDATE XXX SET 'poradi' = 11 WHERE 'poradi' = 10";
</nejaky cyklus v PHP>
$sql .= "UPDATE XXX SET 'poradi' = 10 WHERE 'poradi' = 0";

// V tuto chvili mam "kvanta" SQL prikazu v 1 promenne a zbyva udelat jeden hromadny EXECUTE()

$sql->execute();

Akorat neutisim, zda to, co je obsazeno v to cyklu, je mozne provest v DibiFluent, tj. jak tam naplnit tu promennou?

paranoiq: poradi je na libovuli uzivatele ;o), takze me jina cesta nenapada, nez menit ty kvanta zaznamu, resp. cesta by asi byla pres vytvoreni nejake vlastni logiky zapisu poradi, ale to se mi nechce resit ;o)

před 8 lety

Milo
Nette Core | 1119

Myslím, že to co jsem poslal bude rychlejší. Ale pokud to chceš dělat UPDATE ....; UPDATE ....; UPDATE ....;, poskládat se to dá. Jenže MySQL driver nepodporuje multiquery, takže to přes něj neproleze. Můžeš si upravit MySQLi, psal jsem tu jak cca na to. A další věcí je maximální délka SQL dotazu (nevím jestli je u MySQL nějak omezená), protože pro 1000 řádků to bude román. Takže bych to tak vůbec nedělal :)

$fluent = dibi::command();
for ($i = 10; $i > 0; $i--) {
    $f = dibi::update('XXX', array('poradi' => $i))->where(array('poradi' => $i - 1));
    $fluent->{''}((string) $f . ';');
}

$fluent->test();

před 8 lety

ras
Člen | 127

Diky za info.

Ja to ted resim nad tabulkou o par radcich … takze vykon neni problem … ale umyslne jsem napsal tisic, protoze tam uz vykon je neco, co se musi resit. Tady u tech par radku je to fuk.

Jeste jednou diky.

Radek

před 8 lety

HosipLan
Moderator | 4693

Rozhodně není dobré psát 1000 dotazů pro 1000 řádků, lepší je nastavit to pomocí intervalů.

Ty musíš změnit ty dva konrétní řádky, prohodit je, posunout je… A pak ty zbývající, pokud je potřeba je posouvat, posuneš pomocí

UPDATE xxx SET `order` = `order` + 1 /* nebo -1 */ WHERE `order` <= $x AND `order` > $y

před 8 lety

ras
Člen | 127

Super, ja uplne vedel, ze neco je … ale fakt jsem to nedokazal vymyslet. ;o) To je tak, kdyz ma nekdo Filipa ;o)

před 8 lety

Milo
Nette Core | 1119

@HosipLan: Jenže on má na tom sloupci unikátní klíč. Takže order = order + 1 nemusí proběhnout. Je vhodnější daný interval přesunout nad maximum a pak zpět o jedno větší/menší. Viz. kód k řešení c).

před 8 lety

ras
Člen | 127

Milo …ja to jeste nezkousel → pak dam vedet, ale podle toho HosipLanova popisu bych rekl (logickym usudkem), ze to musi fungovat … az to vyzkousim, tak dam vedet.

před 8 lety

ras
Člen | 127

Takze vyzkouseno → a vysledek jsou 3 dotazy do DB bez ohledu na to, kolik radku se ma menit a UNIQUE index na danem sloupci … klidne to zvladne tech 1000 radku.

Mejme nasledujici tabulku XXX

id / poradi / text
1  /    1   /  aaa
2  /    2   /  bbb
3  /    3   /  ccc
4  /    4   /  ddd
5  /    5   /  eee

**Vzdy je nutne rozlisit, zda je novePoradi vetsi/mensi nez puvodniPoradi → podle toho se pak upravuje update

Pozadavek c.1 … novePoradi < puvodniPoradi

→ eee ma byt prvni → poradi 5 ⇒ 1

// zmenim docasne poradi u radku, ktery se ma primarne zmeni, tj. u poradi c. 5
UPDATE XXX SET `poradi` = 0 WHERE `poradi` = 5
// ted posuneme vsechny radky, kterych se pozadavek na zmenu poradi dotyka, tj. radky v intervalu <nova pozadovana hodnota> az <puvodni hodnota - 1>
// DULEZITE je zde provest to ORDER BY .. jinak by to narazilo na UNIQUE() index
UPDATE XXX SET `poradi` = `poradi` + 1 WHERE `poradi` >= 1 AND `poradi` < 5 ORDER BY poradi DESC;
// nastavime pro radek, ktereho se zmena primarne tyka novou, tj. pozadovanou, hodnotu
UPDATE XXX SET `poradi` = 1 WHERE `poradi` = 0

a ted ukazka opacneho postupu

Pozadavek c.2 … novePoradi > puvodniPoradi

v navaznosti na predchozi pozadavk → eee ma byt ve finale treti → poradi 1 ⇒ 3

// zmenim docasne poradi u radku, ktery se ma primarne zmeni, tj. u poradi c. 1
UPDATE XXX SET `poradi` = 0 WHERE `poradi` = 1
// ted posuneme vsechny radky, kterych se pozadavek na zmenu poradi dotyka, tj. radky v intervalu <puvodni hodnota +1> az <nova pozadovana hodnota>
// DULEZITE je zde provest to ORDER BY .. jinak by to narazilo na UNIQUE() index
UPDATE XXX SET `poradi` = `poradi` - 1 WHERE `poradi` > 1 AND `poradi` <= 3 ORDER BY poradi ASC;
// nastavime pro radek, ktereho se zmena primarne tyka novou, tj. pozadovanou, hodnotu
UPDATE XXX SET `poradi` = 1 WHERE `poradi` = 0

Vysledek po 2 upravach

id / poradi / text
1  /    1   /  aaa
2  /    2   /  bbb
3  /    4   /  ccc
4  /    5   /  ddd
5  /    3   /  eee

= 6 SQL dotazu → mene to myslim ani nejde

Diky vsem za pomoc → pokud nekdo ma v rukavu jeste neco efektivnejsiho, tak sem s tim ;o)

před 8 lety

Milo
Nette Core | 1119

Wow! Tak s tím ORDER BY u mě MySQL dost zabodovalo. To jsem netušil. V tom případě HosipLanovo řešení je to nejsnazší.

Editoval Milo (25. 11. 2011 12:07)

před 8 lety

ras
Člen | 127

Ja taky ne, ale resil jsem, jak je donutit, aby mi to precislovavalo v poradi jak je potreba, jinak to porad narazelo na ten UNIQUE() index → tak jsem to zkusil ;O)

před 8 lety

paranoiq
Člen | 388

ras napsal(a):
paranoiq: poradi je na libovuli uzivatele ;o), takze me jina cesta nenapada, nez menit ty kvanta zaznamu, resp. cesta by asi byla pres vytvoreni nejake vlastni logiky zapisu poradi, ale to se mi nechce resit ;o)

u 20–50 položek bych to ještě chápal. ale v 1000 už se ten chudák přeci musí sám ztrácet. tohle je případ, kdy bych já uživateli vysvětloval, že to co potřebuje asi není to co chce…

před 8 lety

ras
Člen | 127

Ted jeste zkoumam jak to v DibiFluent zapsat

UPDATE XXX SET `poradi` = `poradi` + 1 WHERE `poradi` >= 1 AND `poradi` < 5 ORDER BY poradi DESC;

protoze kdyz napisu

$this->db->update($tabulka, array('poradi' => 'poradi + 1'))->/....

tak je vysledny SQL

UPDATE XXX SET 'poradi' = 'poradi + 1' WHERE ...

a ja potrebuji

UPDATE XXX SET 'poradi' = poradi + 1 WHERE ...

Nejaky navrh by byl, resp. jiny nez to resit pres dibi::query ci dibi::command?

před 8 lety

Milo
Nette Core | 1119

Už se tu několikrát řešilo…

$this->db->update($tabulka, array('poradi%sql' => '[poradi] + 1'))...

před 8 lety

ras
Člen | 127

MILO Pres dibi::command jsem na to vcera prisel …

$this->db->command()->update($tabulka)->where("poradi >= %i", $novePoradi)->AND("poradi < %i", $puvodniPoradi)->orderBy("poradi")->desc()->set("poradi = poradi + 1")->execute();

… ale, protoze jsem vcera musel od PC, tak jsem se k procitani fora nedostal, takze diky za usetreny cas. Tvoje reseni se mi libi vice.

DIKY za vse v tomto vlaknu !