Pregunta

Esto es para MySQL y PHP

Tengo una tabla que contiene las siguientes columnas:

navigation_id (unsigned int primary key)
navigation_category (unsigned int)
navigation_path (varchar (256))
navigation_is_active (bool)
navigation_store_id (unsigned int index)

Los datos se completarán como:

1, 32, "4/32/", 1, 32
2, 33, "4/32/33/", 1, 32
3, 34, "4/32/33/34/", 1, 32
4, 35, "4/32/33/35/", 1, 32
5, 36, "4/32/33/36/", 1, 32
6, 37, "4/37/", 1, 32
... another group that is under the "4/37" node
... and so on

Entonces esto representará un árbol como estructura. Mi objetivo es escribir una consulta SQL que, dada la ID de tienda de 32 y la ID de categoría de 33, devolverá

Primero, un grupo de elementos que son los padres de la categoría 33 (en este caso 4 y 32)

Luego, un grupo de elementos que son hijos de la categoría 33 (en este caso 34, 35 y 36)

Entonces el resto de la raíz "" categorías en la categoría 4 (en este caso 37).

Entonces la siguiente consulta devolverá los resultados correctos:

SELECT * FROM navigation 
WHERE navigation_store_id = 32 
AND (navigation_category IN (4, 32) 
    OR navigation_path LIKE "4/32/33/%/" 
    OR (navigation_path LIKE "4/%/" 
        AND navigation_category <> 32))

Mi problema es que quiero ordenar los "grupos" de categorías en el orden mencionado anteriormente (padres de 33 primeros, hijos de 33 segundos y padres del nodo raíz último). Entonces, si cumplen con la primera condición, ordénelos primero, si cumplen con la segunda condición, ordénelos en segundo lugar y si cumplen con la tercera (y cuarta) condición, ordénelos en último lugar.

Puede ver un ejemplo de cómo funciona la estructura de categorías en este sitio:

www.eanacortes.net

Puede notar que es bastante lento. La forma actual en que hago esto es usar la tabla de categorías original de magento y ejecutar tres consultas particularmente lentas; luego juntando los resultados en PHP. Al usar esta nueva tabla, estoy resolviendo otro problema que tengo con magento, pero también me gustaría mejorar mi rendimiento al mismo tiempo. La mejor forma en que veo que esto se logra es unir las tres consultas y hacer que PHP funcione menos al ordenar los resultados correctamente.

Gracias

EDIT

Muy bien, funciona muy bien ahora. Reducirlo de 4 segundos a 500 MS. Gran velocidad ahora :)

Aquí está mi código en la clase Colleciton:

    function addCategoryFilter($cat)
    {
        $path = $cat->getPath();
        $select = $this->getSelect();
        $id = $cat->getId();
        $root = Mage::app()->getStore()->getRootCategoryId();
        $commaPath = implode(", ", explode("/", $path));

        $where = new Zend_Db_Expr(
            "(navigation_category IN ({$commaPath}) 
                    OR navigation_parent = {$id}
                    OR (navigation_parent = {$root}
                    AND navigation_category <> {$cat->getId()}))");

        $order = new Zend_Db_Expr("
                CASE
                    WHEN navigation_category IN ({$commaPath})  THEN 1
                    WHEN navigation_parent = {$id} THEN 2
                    ELSE 3
                END, LENGTH(navigation_path), navigation_name");

        $select->where($where)->order($order);
        return $this;
    }

Luego lo consumo con el siguiente código que se encuentra en mi bloque de Categoría:

        // get our data
        $navigation = Mage::getModel("navigation/navigation")->getCollection();
        $navigation->
            addStoreFilter(Mage::app()->getStore()->getId())->
            addCategoryFilter($currentCat);

        // put it in an array
        $node = &$tree;
        $navArray = array();
        foreach ($navigation as $cat)
        {
            $navArray[] = $cat;
        }
        $navCount = count($navArray);

        $i = 0;

        // skip passed the root category
        for (; $i < $navCount; $i++)
        {
            if ($navArray[$i]->getNavigationCategory() == $root)
            {
                $i++;
                break;
            }
        }

        // add the parents of the current category
        for (; $i < $navCount; $i++)
        {
            $cat = $navArray[$i];

            $node[] = array("cat" => $cat, "children" => array(), 
                "selected" => ($cat->getNavigationCategory() == $currentCat->getId()));
            $node = &$node[0]["children"];

            if ($cat->getNavigationCategory() == $currentCat->getId())
            {
                $i++;
                break;
            }
        }

        // add the children of the current category
        for (; $i < $navCount; $i++)
        {
            $cat = $navArray[$i];
            $path = explode("/", $cat->getNavigationPath());
            if ($path[count($path) - 3] != $currentCat->getId())
            {
                break;
            }

            $node[] = array("cat" => $cat, "children" => array(), 
                "selected" => ($cat->getNavigationCategory() == $currentCat->getId()));
        }

        // add the children of the root category
        for (; $i < $navCount; $i++)
        {
            $cat = $navArray[$i];
            $tree[] = array("cat" => $cat, "children" => array(), 
                "selected" => ($cat->getNavigationCategory() == $currentCat->getId()));
        }

        return $tree;

Si pudiera aceptar dos respuestas, aceptaría la primera y la última, y ??si pudiera aceptar una respuesta como "interesante / útil". Yo haría eso con el segundo. :)

¿Fue útil?

Solución

Una expresión CASE debería hacer el truco.

SELECT * FROM navigation 
    WHERE navigation_store_id = 32 
        AND (navigation_category IN (4, 32) 
            OR navigation_path LIKE "4/32/33/%/" 
            OR (navigation_path LIKE "4/%/" 
            AND navigation_category <> 32))
    ORDER BY
        CASE
            WHEN navigation_category IN (4, 32) THEN 1
            WHEN navigation_path LIKE "4/32/33/%/" THEN 2
            ELSE 3
        END, navigation_path

Otros consejos

Pruebe una columna derivada adicional como " weight " ;:

(untested)

(IF(criteriaA,1,0)) + (IF(criteriaB,1,0)) ... AS weight
....
ORDER BY weight 

Cada criterio aumenta el "peso" por el estilo. También puede establecer los pesos de forma distinta anidando los IF y dando a los grupos un número entero particular para ordenar por:

IF(criteriaA,0, IF(criteriaB,1, IF ... )) AS weight

¿MySQL tiene la palabra clave SQL UNION para combinar consultas? Sus tres consultas tienen principalmente criterios que no se superponen, por lo que sospecho que es mejor dejarlas como consultas esencialmente separadas, pero combinarlas usando UNION o UNION ALL . Esto ahorrará 2 viajes de ida y vuelta de DB y posiblemente facilitará que el planificador de consultas de MySQL "vea". la mejor manera de encontrar cada conjunto de filas es.

Por cierto, su estrategia de representar el árbol almacenando rutas desde la raíz hasta la punta es fácil de seguir, pero bastante ineficaz siempre que necesite usar una cláusula WHERE del formulario navigation_path como '% XYZ' - en todos los DB que he visto, las condiciones LIKE deben comenzar con un comodín para permitir el uso de un índice en esa columna. (En su fragmento de código de ejemplo, necesitaría una cláusula de este tipo si aún no sabía que la categoría raíz era 4 (¿Cómo lo sabía por cierto? ¿De una consulta anterior separada?))

¿Con qué frecuencia cambian sus categorías? Si no cambian con frecuencia, puede representar su árbol utilizando los "conjuntos anidados". método, descrito aquí , que permite consultas mucho más rápidas sobre cosas como " ¿Qué categorías son descendientes / antepasados ??de una categoría determinada " ;.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top