【问题标题】:Mysql query which returns category tree返回类别树的Mysql查询
【发布时间】:2012-07-12 09:14:13
【问题描述】:

我正在从事品类管理工作。我在每一行都有 parentId。类别可以是 n 级。 我可以递归调用 php 函数,该函数将返回类别树数组。

现在的问题是:在管理面板中,我想要如下所示的类别列表页面。即属于哪个类别。

  • 首页
  • 产品(编辑)(删除)
  • 产品 > 产品 1(编辑)(删除)
  • 产品 > 产品 2(编辑)(删除)
  • 产品 > 产品 2 > 产品 2 1(编辑)(删除)
  • 产品 > 产品 2 > 产品 2 2(编辑)(删除)
  • 联系我们(编辑)(删除)

我希望 mysql 查询结果的顺序如上所示。我不确定如何实现它。

SELECT *
FROM tbl_categories
ORDER BY ???

请指导。

【问题讨论】:

  • 更好地使用服务器编程语言。
  • 它正在与编程一起工作。列表中可以有分页,我想对该查询使用限制。我在购物车开源的某个地方看到过,但我不记得他们是怎么做的。
  • 请向我们展示表结构,也就是说,我想要 2 得到 Product 2.1 行
  • 如果需要,我可以在表中添加其他字段,即 category_depth,..或任何其他字段。可以在查询中使用。
  • 好吧,以目前的表结构,没有单一的 SQL 查询可以为您带来正确排序的结果。要么使用具有递归调用的应用程序逻辑来发现每个级别的子级,要么您必须修改表(请参阅上面有关 MPTT 的信息)

标签: php mysql sql-order-by categories


【解决方案1】:

没有单个 SQL 查询可以根据此表结构为您带来按您期望的方式排序的结果。

解决问题的方法有两种:

  1. 使用外部应用程序逻辑(在 DB 之外)进行递归调用,以发现每个类别的子项并在应用程序中构建树。

  2. 使用一种算法将树数据存储在关系数据库中。 其中一种算法称为Modified Preorder Tree Traversal 或简称为 MPTT。

假设我们使用列 lftrgt 来维护遍历中的左/右索引,当您插入新类别时,您需要:

  1. 通过 ID 获取父类别信息:SELECT lft,rgt FROM tbl_categories WHERE categoryId=5 让我们假设一个例子,父类别有lft=7rgt=10(在这种情况下它已经有一个孩子)

  2. 为新条目腾出空间 - 将所有记录移动 2(lft 为 1,rgt 为 1):

    UPDATE tbl_categories SET rgt=rgt+2 WHERE rgt>=10 ORDER BY rgt DESC

    UPDATE tbl_categories SET lft=lft+2 WHERE lft>=10 ORDER BY lft DESC

注意这里ORDER 降序。由于lftrgt 应该是唯一的,所以建议对它们进行UNIQUE 约束,然后在更新时需要降序排列以防止重复键错误。

  1. 设置lft=<former parent rgt>rgt=<former parent rgt +1>并插入一条新记录...

    INSERT INTO tbl_categories SET categoryName="New Child",parentCategoryId=5,lft=11,rgt=12,...

如果您搜索MPTT PHP MySQL,您可以找到更详细的代码示例。有很多关于这个主题的教程。

【讨论】:

  • @VibhaJ:另一种选择是关闭表:karwin.blogspot.de/2010/03/…,它的额外好处是您可以使用外键来确保父类别确实存在。
  • 顺便说一句,我通常使用 MPTT,同时仍将父级(我什至添加深度)保留在表中,这样就没有找到直系亲属的陷阱......
  • 在大多数情况下,我认为闭包模型比 MPTT(也称为嵌套集)模型更有效。在树结构周围插入和移动节点需要较少的操作。
  • 获取整个树/子树怎么样? MPTT 通过简单的选择(没有连接)很容易做到这一点。另外(恕我直言)闭包表(对于人类)更难查看,因为主实体表丢失了任何结构信息,并且关系表本身根本不是人类可读的......
【解决方案2】:

您可以使用一个存储函数,该函数将递归地获取根路径并与您的父级连接。

DELIMITER $$
DROP FUNCTION IF EXISTS `get_category`$$
CREATE FUNCTION `get_category`(cat_id int) RETURNS VARCHAR(255)
READS SQL DATA
BEGIN
DECLARE c_id INT;
DECLARE p_id INT;
DECLARE count INT;
DECLARE cat_name VARCHAR(255);
DECLARE cat_path VARCHAR(255);
set c_id = cat_id;
SELECT parent_category_id, category_name INTO p_id, cat_name FROM categories WHERE category_id = c_id;
set c_id = p_id;
set cat_path=cat_name;
set count=0;
WHILE (c_id IS NOT NULL) DO 

SELECT parent_category_id, category_name INTO p_id, cat_name FROM categories WHERE category_id = c_id;
set c_id = p_id;
set cat_path = concat_ws('>',cat_name,cat_path);
set count = count + 1;
IF count=10 THEN
  SET c_id = NULL;
END IF;
 END WHILE;
RETURN cat_path;
END
$$
DELIMITER ;

然后使用

调用
select getcategory(category_id);

在此函数中,我进行了完整性检查以避免无限循环..

【讨论】:

  • 虽然这会发现单个类别的路径,所以如果您要获取一棵树,这将需要为每个结果调用它......但无论如何都很好;)
  • 它只是一个原型,您甚至可以将其进一步修改为一个过程,带有一个光标,并循环到每个类别树..并继续..我相信它可以完成
【解决方案3】:

您无法在单个 mysql 查询中实现此目的。 那么你可以通过执行多个查询来实现这一点。 算法是这样的: 通过从数据库中获取数据,最初创建一个您将填充的数据集对象。 创建一个方法,该方法将父 id 作为参数,如果存在则返回其子节点,如果没有子节点,则返回 -1。 Step1:获取所有没有父(根)节点的行。 第2步: 遍历这个结果。 例如,如果 prod1 和 prod2 是结果集中的初始返回节点。迭代这个 RS 我们得到 prod1,我们在 DataSET obj 中插入一行。然后我们将 prod1 的 id 发送给 getCHILD 方法,以获取它的子节点,然后我们再次迭代返回的结果集,并再次调用 getCHILD 方法,直到我们没有得到最低节点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 2014-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多