Создайте SQL-запрос, который упорядочивает результаты по тому условию, которому они соответствуют

StackOverflow https://stackoverflow.com/questions/475178

  •  19-08-2019
  •  | 
  •  

Вопрос

Это для MySQL и PHP

У меня есть таблица, которая содержит следующие столбцы:

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

Данные будут заполнены следующим образом:

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

Таким образом, это будет представлять собой древовидную структуру.Моя цель - написать SQL-запрос, который, учитывая идентификатор магазина 32 и идентификатор категории 33, вернет

Во-первых, группа элементов, которые являются родителями категории 33 (в данном случае 4 и 32)

Затем создается группа элементов, являющихся дочерними элементами категории 33 (в данном случае 34, 35 и 36).

Затем остальные "корневые" категории под категорией 4 (в данном случае 37).

Таким образом, следующий запрос вернет правильные результаты:

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

Моя проблема в том, что я хочу упорядочить "группы" категорий в порядке, указанном выше (родители 33 первых, дочерние элементы 33 вторых и родители корневого узла последними).Поэтому, если они удовлетворяют первому условию, заказывайте их первыми, если они удовлетворяют второму условию, заказывайте их вторыми, а если они удовлетворяют третьему (и четвертому) условию, заказывайте их последними.

Вы можете увидеть пример того, как работает структура категорий на этом сайте:

www.eanacortes.net

Вы можете заметить, что это довольно медленно.В настоящее время, как я это делаю, я использую оригинальную таблицу категорий magento и выполняю к ней три особенно медленных запроса;затем объединяем результаты в PHP.Используя эту новую таблицу, я решаю еще одну проблему, которая возникла у меня с magento, но в то же время хотел бы улучшить свою производительность.Лучший способ, на мой взгляд, достичь этого, - объединить все три запроса вместе и заставить PHP работать меньше за счет правильной сортировки результатов.

Спасибо

Редактировать

Хорошо, теперь это отлично работает.Сократите время с 4 секунд до 500 МС.Теперь отличная скорость :)

Вот мой код в классе 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;
    }

Затем я использую его со следующим кодом, найденным в моем блоке категорий:

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

Если бы я мог принять два ответа, я бы принял первый и последний, и если бы я мог принять ответ как "интересный / полезный", я бы сделал это со вторым.:)

Это было полезно?

Решение

A CASE выражение должно сделать свое дело.

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

Другие советы

Попробуйте создать дополнительный производный столбец, например "вес".:

(непроверенный)

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

Каждый критерий увеличивает "вес" сортировки.Вы также могли бы четко установить веса, вложив IFS и присвоив группам определенное целое число для сортировки по like:

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

Есть ли у MySQL UNION Ключевое слово SQL для объединения запросов?Ваши три запроса имеют в основном непересекающиеся критерии, поэтому я подозреваю, что лучше оставить их как по существу отдельные запросы, но объединить их с помощью UNION или UNION ALL.Это сэкономит 2 БД на обходах туда и обратно и, возможно, упростит планировщику запросов MySQL "увидеть" наилучший способ поиска каждого набора строк is.

Кстати, вашей стратегии представления дерева путем сохранения путей от корня до вершины легко следовать, но довольно неэффективно всякий раз, когда вам нужно использовать предложение WHERE формы navigation_path like '%XYZ' -- на всех DBS, которые я видел, LIKE условия должны начинаться с символа, не являющегося шаблоном, чтобы разрешить использование индекса в этом столбце.(В вашем примере фрагмента кода вам понадобилось бы такое предложение, если бы вы еще не знали, что корневая категория равна 4 (кстати, как вы это узнали?Из отдельного, более раннего запроса?))

Как часто меняются ваши категории?Если они меняются не часто, вы можете представить свое дерево, используя метод "вложенных множеств", описанный здесь, что позволяет гораздо быстрее выполнять запросы по таким вещам, как "Какие категории являются потомками / предками данной категории".

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top