【问题标题】:Create SQL query that orders results by which condition they meet创建 SQL 查询,根据满足的条件对结果进行排序
【发布时间】:2010-10-03 06:36:56
【问题描述】:

这适用于 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 查询,给定商店 ID 32 和类别 ID 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;

如果我能接受两个答案,我会接受第一个和最后一个,如果我能接受一个“有趣/有用”的答案,我会接受第二个。 :)

【问题讨论】:

  • 你可能会自己加入,或者编写一个脚本来将数据库转换为关系型
  • 听起来你一开始就选择了错误的逻辑来达到这一点。三个慢查询然后与 PHP 合并?你需要重写那部分。
  • 听起来你一开始就选择了错误的逻辑来达到这一点。三个慢查询然后与 PHP 合并?你需要重写那部分。谁是 magento 显然给了你次优的建议?

标签: php sql mysql magento


【解决方案1】:

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

【讨论】:

  • 我会接受这个答案,因为代码很好用!感谢您回答这个问题,我对在 ORDER 语句中使用 CASE 进行了一些研究,并学习了一些新技巧:)
【解决方案2】:

尝试额外的派生列,例如“重量”:

(未经测试)

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

每个条件都会增加排序的“权重”。 您还可以通过嵌套 IF 并给组一个特定的整数来明确设置权重,以便按如下方式排序:

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

【讨论】:

    【解决方案3】:

    MySQL 是否有用于组合查询的UNION SQL 关键字?您的三个查询主要具有不重叠的标准,因此我怀疑最好将它们保留为本质上独立的查询,但使用UNIONUNION ALL 将它们组合起来。这将节省 2 次 DB 往返,并可能使 MySQL 的查询计划器更容易“看到”查找每组行的最佳方法。

    顺便说一句,您通过存储从根到尖端的路径来表示树的策略很容易遵循,但是当您需要使用 navigation_path like '%XYZ' 形式的 WHERE 子句时效率很低——在我见过的所有数据库上, LIKE 条件必须以非通配符开头,才能在该列上使用索引。 (在您的示例代码 sn-p 中,如果您还不知道根类别是 4,您将需要这样一个子句(顺便说一句,您是怎么知道的?从一个单独的、较早的查询中?))

    您的类别多久更改一次?如果它们不经常更改,您可以使用“嵌套集”方法表示您的树,如 here 所述,该方法可以更快地查询“哪些类别是给定类别的后代/祖先”之类的内容。

    【讨论】:

    • 在我的示例中,4 被认为是商店的根类别。它包含在存储配置表中,并在页面加载开始时获取。不过,我可能应该更清楚地说明这一点。也感谢您的链接:)
    • 我明白了。很高兴你喜欢这个链接,我认为这是一种非常酷的方式来表示一棵树,当然不是我自己想出来的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-27
    • 2013-08-09
    • 2019-11-18
    • 2011-09-28
    • 2017-05-09
    • 2016-03-17
    相关资源
    最近更新 更多