Pergunta

Isto é para MySQL e PHP

Eu tenho uma tabela que contém as seguintes colunas:

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

Os dados serão preenchidos 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

Então, isso vai representar uma árvore como estrutura. Meu objetivo é escrever uma consulta SQL que, dada a ID de armazenamento de 32 e ID da categoria de 33, vai voltar

Primeiro, um grupo de elementos que são os pais da categoria 33 (neste caso 4 e 32)

Em seguida, um grupo de elementos que são uma criança de categoria 33 (neste caso 34, 35, e 36)

Em seguida, o resto das categorias "raiz" na categoria 4 (neste caso 37).

Assim, a seguinte consulta retornará os resultados corretos:

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))

O meu problema é que eu quiser encomendar os "grupos" de categorias na ordem listada acima (pais de 33 primeiramente, crianças de 33 segundo, e os pais do nó raiz passado). Então, se eles preenchem a primeira condição, encomendá-los em primeiro lugar, se cumprirem a segunda ordem condição los segundo e se cumprirem o terceiro (e quarto) ordem condição deles passado.

Você pode ver um exemplo de como a estrutura de categoria trabalha neste site:

www.eanacortes.net

Você pode notar que é bastante lento. A forma atual que estou fazendo isso eu estou usando a tabela categoria original do magento e execução de três consultas particularmente lenta em que; em seguida, colocar os resultados juntos em PHP. Usando esta nova tabela estou resolvendo outro problema que eu tenho com o Magento, mas também gostaria de melhorar o meu desempenho, ao mesmo tempo. A melhor maneira que eu vejo esta sendo realizado é colocar todas as três consultas junto e que têm trabalho PHP menos por ter os resultados classificadas corretamente.

Graças

Editar

Tudo bem, ele funciona muito bem agora. Cortá-la a partir de 4 segundos para baixo para 500 ms. Grande velocidade agora:)

Aqui está o meu código na classe 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;
    }

Então eu consumi-lo com o seguinte código encontrado no meu bloco Categoria:

        // 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;

Se eu pudesse aceitar duas respostas eu aceitaria a primeira e última, e se eu poderia aceitar uma resposta como "interessante / útil" Eu faria isso com o segundo. :)

Foi útil?

Solução

A expressão CASE deve fazer o truque.

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

Outras dicas

Tente uma coluna derivada adicional como "peso":

(não testado)

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

Cada critério aumenta o "peso" do tipo. Você também pode definir os pesos distintamente pelos FI nidificação e dando os grupos um inteiro particular para ordenar por como:

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

O MySQL tem a palavra-chave SQL UNION para combinar consultas? Seus três consultas têm principalmente critérios não sobrepostas, então eu suspeito que é melhor deixá-los como consultas essencialmente separados, mas combiná-los usando UNION ou UNION ALL. Isto vai poupar 2 dB round-trips, e possivelmente torná-lo mais fácil para planejador de consultas do MySQL para "ver" a melhor maneira de encontrar cada conjunto de linhas é.

A propósito, a sua estratégia de representar a árvore armazenando caminhos da raiz à ponta é fácil de seguir, mas bastante ineficiente sempre que você precisa usar uma cláusula WHERE da navigation_path like '%XYZ' forma - em todos os bancos de dados que eu vi, LIKE condições devem começar com um não-universal para permitir a utilização de um índice em que coluna. (No seu exemplo trecho de código, você precisaria de uma cláusula desse tipo, se você já não soubesse que a categoria raiz foi de 4 (Como você sabia que pelo caminho? De uma consulta separada, mais cedo?))

Como costumam fazer sua mudança de categorias? Se eles não mudam com frequência, pode representar sua árvore usando o método de "conjuntos aninhados", descreveu aqui , que permite muito mais rápida consulta em coisas como "que categorias são descendentes / ancestrais de uma determinada categoria".

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top