Rekurzivní uložení kategorií, řazení – pomoc s logikou věci

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
plasmo
Člen | 66
+
0
-

Ahoj,
potřebuju do db ukládat kategorie rekurzivně. Nyní už mám tabulku stylu:

id_category parent level order name

Ukládání je v pohodě, výpis také :-), ale nedokázal jsem pořešit následující věc:
potřebuju aby se kategorie řadili podle názvu a také byla možnost volitelně si je přeházet.

Pro vkládání kategorií sem spáchal následující metodu (asi mi to omlátíte o hlavu, jak by se to dalo napsat lépe? jsem rád že jsem vymyslel vůbec tuto hrůzu)

<?php
public function createCategory(Category $category)
    {
        try{
            if($category->parent == 0){
                $result = dibi::query('SELECT [order] FROM categories ORDER BY [order] DESC LIMIT 1');
                $value = $result->fetchSingle();
                unset($result);
                $category->order = $value+1;
                $category->level = 1;
            }else{
                $result = dibi::query('SELECT level FROM categories WHERE id_category = %i ORDER BY id_category DESC LIMIT 1',$category->parent);
                $category->level = $result->fetchSingle()+1;

                $result = dibi::query('SELECT count(*) as pocet FROM categories WHERE parent = %i ORDER BY id_category ASC LIMIT 1',$category->parent);
                if($result->fetchSingle() > 0){
                    $result = dibi::query('SELECT [order] FROM categories WHERE parent = %i ORDER BY id_category DESC LIMIT 1',$category->parent);
                    $category->order = $result->fetchSingle()+1;
                }elseif($result->fetchSingle() == 0){
                    $result = dibi::query('SELECT [order] FROM categories WHERE id_category = %i ORDER BY id_category DESC LIMIT 1',$category->parent);
                    $category->order = $result->fetchSingle()+1;
                }

                $result = dibi::query("SELECT id_category,[order] FROM categories WHERE [order] >= '".$category->order."'");
                foreach ($result as $n => $row) {
                    dibi::query("UPDATE categories SET [order] = '".($row['order']+1)."' WHERE id_category = %i",$row['id_category']);
                }
                unset($result);
            }

            $cat = array("parent"=>$category->parent,"level"=>$category->level,"order"=>$category->order);
            dibi::query("INSERT INTO categories ",$cat);
            $lang = array("id_category"=>dibi::insertId(),"lang"=>$category->lang,"name"=>$category->name,"seo"=>$category->seo);
            dibi::query("INSERT INTO categories_lang ",$lang);

            /*
            $result = dibi::query("
                    SELECT * FROM categories
                    LEFT JOIN categories_lang
                    ON categories.id_category = categories_lang.id_category
                    WHERE [categories.parent] = '".$category->parent."' AND categories_lang.lang = 4 ORDER BY [name] ASC");
            foreach ($result as $n => $row) {
                    dibi::query("UPDATE categories SET [order] = '".$category->order."' WHERE id_category = %i",$row['id_category']);
                    $category->order = $category->order+1;
            }*/

        }catch(DibiException $e){
            if($e->getCode() == 1062){
                throw new InvalidStateException('Kategorie se stejným názvem již existuje');
            }else{
                throw new InvalidStateException('Při zápisu do databáze došlo k chybě!'.$e->getMessage());
            }
        }
    }
?>

Toto mi zajístí tak že se mi vždy nová kategorie vloží nakonec dané úrovně, pokud bych je chtěl seřadit podle ABC, musím odkomentovat tento kus kódu:

<?php
$result = dibi::query("
                    SELECT * FROM categories
                    LEFT JOIN categories_lang
                    ON categories.id_category = categories_lang.id_category
                    WHERE [categories.parent] = '".$category->parent."' AND categories_lang.lang = 4 ORDER BY [name] ASC");
            foreach ($result as $n => $row) {
                    dibi::query("UPDATE categories SET [order] = '".$category->order."' WHERE id_category = %i",$row['id_category']);
                    $category->order = $category->order+1;
            }
?>

Vlastní řazení mi zajišťují tyto dvě metody:

<?php
public function moveUp($id){
        $result = dibi::query('SELECT [order] FROM categories WHERE [id_category] = %i LIMIT 1',$id);
        $order = $result->fetchSingle();
        dibi::query("UPDATE categories SET [order] = [order] +1 WHERE [order] = %i LIMIT 1",($order-1));
        dibi::query("UPDATE categories SET [order] = [order] -1 WHERE [id_category] = %i LIMIT 1",$id);
    }

    public function moveDown($id){
        $result = dibi::query('SELECT [order] FROM categories WHERE [id_category] = %i LIMIT 1',$id);
        $order = $result->fetchSingle();
        dibi::query("UPDATE categories SET [order] = [order] -1 WHERE [order] = %i LIMIT 1",($order+1));
        dibi::query("UPDATE categories SET [order] = [order] +1 WHERE [id_category] = %i LIMIT 1",$id);
    }
?>

Takhle mi vše funguje jak má, ale je tu háček, když si po vytvoření strukutury, která se seřadí podle ABC přeházím ručně pár kategorií a následně si vzpomenu že potřebuju přidat kategorii tak se mi kategori opět seřadí dle ABC a mé řazení se zruší.

Dále, pokud mát takovouhle strukturu:

-Notebooky
-- 13"
-- 15"
-- 17"
-PC sestavy
--Herní
--Kancelářské

A u kategorie Notebooky dám posun dolů, vznikne mi toto:

-- 13"
-Notebooky
-- 15"
-- 17"
-PC sestavy
--Herní
--Kancelářské

A já chci aby vzniklo toto:

-PC sestavy
--Herní
--Kancelářské
-Notebooky
-- 13"
-- 15"
-- 17"

Asi mám někde logickou chybu,ležím v tom celý den a už si fatk nevím rady. Jak bych to měl řešit? Díky moc

na1k
Člen | 288
+
0
-

Taky jsem psal neomezeně zanořitelné kategorie pomocí parent, level a order a užil si s tím svoje :))

Nemám teď sílu zkoumat tvůj kód, ale doporučuju rozdělit si to na triviální úkony a ty pak otestovat na všechny možné kombinace úkonů a pokračovat dál, až budou všechny dílčí kódy dokonale odladěné. Anebo ještě lépe – přejít na nějakou stromovou strukturu, ty jsou (alespoň dle mého) lépe a jednodušeji udržovatelné. V takovém případě mrkni na web Jakuba Vrány – traverzování kolem stromu.

Pokud chceš zůstat u současné podoby uložených dat, můžeš se zkusit inspirovat v mém (již starším) modelu. Mělo by tam být všechno funkční. Obsahuje (pokud dobře pamatuju) operace – přidat (pod libovolného rodiče), upravit (vč. změny rodiče), smazat, posunout v pořadí.

Obsahuje to i komentáře a pomocné dumpy, takže myslím že se v tom vyznáš ;-)

Za zvýraznění se omlouvám, nějak to odmítá spolupracovat :-/
https://gist.github.com/708020

Editoval na1k (20. 11. 2010 19:15)

plasmo
Člen | 66
+
0
-

Děkuju, dal jsem to do kupy až na řazení dle abecedy, s kterým teď zápasím.

Udělal jsem to tak že nově přidané kategorie se zařadí vždy jako poslední na dané úrovni pod svého rodiče, uživatel si může následně kategorie přeskládat dle libosti. Řazení podle ABC udělám odkazem Seřadit dle ABC, jediný co se mi ještě nedaří je dát do kupy algoritmus který mi to vše hezky seřadí a zachová strukturu

Ještě jsem narazil na jednu věc, a to stránkování. Asi se nedá nijak ošetřit aby se mi nestávalo toto:
Strana1

-PC sestavy
--Herní
--Kancelářské
-Notebooky
-- 13"

Strana2

-- 15"
-- 17"

Děkuji za pomoc

na1k
Člen | 288
+
0
-

K tomu si budeš muset předpočítat limity a offsety, protože klasický přístup (ala paginator) ti bude dělat to co jsi teď napsal.

Já bych asi použil nějakou funkci, která by mi ty limity vrátila pro celou tabulku. Předhodil bych ji doporučený počet položek na stránku a ona by se snažila k němu dostat co nejblíž (aniž by přitom dělila stránku v půlce zanořených položek). Výsledkem by mohly být třeba jen indexy položek, u kterých se má „zalomit“ stránka, a z toho by se už pak daly odvodit limit a offset pro samotný dotaz. Detaily implementace už záleží na tom, jak si představuješ aby to dělilo – jen na nejvyšší úrovni, hlouběji, všude,…

plasmo
Člen | 66
+
0
-

Děkuji ti, ještě mi zbývá vyřešit to stránkování. Řazení dle abecedy jsem právě dal do kupy a funguje :)