使用LIKE的递归SELECT类别面包屑在单个查询中

时间:2015-12-15 10:56:58

标签: php mysql

我正在使用eBay类别,我正在寻找一种最有效的方法来检索匹配的"叶子类别列表#34;(仅限顶级),当给出一个术语时,其完整的面包屑匹配类别名称的一部分

这是我一直在使用的sqlfiddle

假设我只有两个Leaf类别(1900年后和1900年前)

这是他们的面包屑

Antiques > Antique Clocks > Bracket Clocks > Post-1900
Antiques > Antique Clocks > Bracket Clocks > Pre-1900

如果术语" Bracket"然后使用结果将包含两行,每个面包屑一行,但是如果" Post-19"这个术语只返回一行。 每行应包含两个字段CategoryIDbreadcrumb,CategoryID必须是" leaf类别"。

CREATE TABLE `ebay_categories` (
  `CategoryID` int(11) DEFAULT NULL,
  `CategoryName` varchar(20) DEFAULT NULL,
  `CategoryParentID` int(11) DEFAULT NULL,
  `CategoryLevel` int(11) DEFAULT NULL
);

insert into `ebay_categories` (`CategoryID`, `CategoryName`, `CategoryParentID`, `CategoryLevel`) values('20081','Antiques','20081','1');
insert into `ebay_categories` (`CategoryID`, `CategoryName`, `CategoryParentID`, `CategoryLevel`) values('13851','Antique Clocks','20081','2');
insert into `ebay_categories` (`CategoryID`, `CategoryName`, `CategoryParentID`, `CategoryLevel`) values('100904','Bracket Clocks','13851','3');
insert into `ebay_categories` (`CategoryID`, `CategoryName`, `CategoryParentID`, `CategoryLevel`) values('96762','Post-1900','100904','4');
insert into `ebay_categories` (`CategoryID`, `CategoryName`, `CategoryParentID`, `CategoryLevel`) values('66840','Pre-1900','100904','4');

我试图实施与here相同的方法,但一直在失败。

SELECT LeafID as CategoryID, GROUP_CONCAT(CategoryName SEPARATOR ' > ') AS breadcrumb FROM (

    (SELECT CategoryID as LeafID AS 

       SELECT * from ebay_categories WHERE CategoryName LIKE '%Antiq%')  AS c

  ) AS b GROUP BY LeafID

) AS a ORDER BY breadcrumb ASC Limit 20

1 个答案:

答案 0 :(得分:0)

SQL只能(没有"聪明"使用存储过程等)返回固定数量的列。

(仅供参考 - 您链接到的答案,恕我直言,#34;聪明"。它正在使用会话变量等做一些非常明显的事情,如果他们没有&#39 ;伤害表现会伤害可读性 - 所以我会尝试从不同角度回答你的问题。)

因此,您可以修复(硬编码)面包屑" depth",并且使用固定数量的JOIN语句,一切都变得非常简单。

我假设面包屑深度是1到无穷大之间的任何东西?即另一个"集合"项目可以在较小深度的类别下提交?

在这种情况下,您的GROUP_CONCAT可能是解决方案的一部分,因为它可以模拟"变量列数"在SQL中。 (它返回1列,但内部可以包含灵活数量的分隔值。)

您的问题仍然是SQL的性质仍然只能根据JOIN语句将一个表连接到另一个(单个)表。您的面包屑数据结构已经很好地规范化,并且假设每个子类别都是其父级的连接。

可以尝试动态构建SQL - 但这可能会烧毁你。你可能会留下两个"显而易见的"选项:

  1. 存储过程。 (超越基本SQL。)
  2. 更改架构。 (在存储数据时解决问题,而不是检索。)
  3. 存储过程可以通过多种方式解决这个问题 - 一个显而易见的选择是迭代地逐步构建每个面包屑,将范围存储在临时表中,然后最终选择整个临时表。

    我很乐意为您提供指导,但我不会将此作为此答案的一部分(除非有要求),因为我相当确定您的表现会非常糟糕最终不想使用它。

    其他"主要"然后选项是重构架构。在这种情况下,您实现的标准化程度正在使事情变得过于复杂。它在学术上是好的"它对磁盘空间有好处。但它不能很好地解决你的问题!

    Denormalising有另一个主要的权衡。在更改架构中的数据时,您会有更多的复杂性。我建议首先编写一个"重建"数据(如果你采用这种方法),因为否则事情将会失去同步,你将永远花费在尝试解决出错的地方。 (我从经验中说。)

    对于每个匹配的记录(您将用户输入与CategoryName进行比较),您希望返回并能够在树之前的所有内容进行分组。并且没有做到"聪明"东西。

    一种(几种)非规范化方法是为祖先保留depth * width长的叶子列表。 (就像我说的那样,它不具有存储效率。您必须评估这是否是生产场景中的问题。)对于您的示例数据,它将如下所示:

    +------------+--------+
    | AncestorId | LeafId |
    +------------+--------+
    | 20081      | 66840  |
    | 20081      | 96762  |
    | 13851      | 66840  |
    | 13851      | 96762  |
    | 100904     | 66840  |
    | 100904     | 96762  |
    | 66840      | 66840  |
    | 96762      | 96762  |
    +------------+--------+
    

    因此现在你可以这样做:

    CREATE TABLE `tree_branches` (
      `AncestorId` int(11) NOT NULL,
      `LeafId` int(11) NOT NULL
    );
    
    INSERT INTO `tree_branches` SET `AncestorId`=20081, `LeafId`=66840;
    INSERT INTO `tree_branches` SET `AncestorId`=20081, `LeafId`=96762;
    INSERT INTO `tree_branches` SET `AncestorId`=13851, `LeafId`=66840;
    INSERT INTO `tree_branches` SET `AncestorId`=13851, `LeafId`=96762;
    INSERT INTO `tree_branches` SET `AncestorId`=100904, `LeafId`=66840;
    INSERT INTO `tree_branches` SET `AncestorId`=100904, `LeafId`=96762;
    INSERT INTO `tree_branches` SET `AncestorId`=66840, `LeafId`=66840;
    INSERT INTO `tree_branches` SET `AncestorId`=96762, `LeafId`=96762;
    
    SELECT
      GROUP_CONCAT(`breadCrumbCategories`.`CategoryName` SEPARATOR " > ")
    FROM `ebay_categories` AS `matchedCategory`
    INNER JOIN `tree_branches` AS `matchedCategoryLeaves` ON (`matchedCategoryLeaves`.`AncestorId` = `matchedCategory`.`categoryId`)
    INNER JOIN `tree_branches` AS `breadCrumbs` ON (`breadCrumbs`.`LeafId` = `matchedCategoryLeaves`.`LeafId`)
    INNER JOIN `ebay_categories` AS `breadCrumbCategories` ON (`breadCrumbCategories`.`CategoryId` = `breadCrumbs`.`ancestorId`)
    WHERE
      `matchedCategory`.`CategoryName` LIKE "Post%"
    GROUP BY
      `breadCrumbs`.`LeafId`
      ;
    

    您应该为GROUP_BY添加某种排序,以确保它不会做出隐含意外的事情。您可以(例如)为此目的维护级别ID。

    <强>更新 一旦你理解了我上面所做的事情,就应该用LIKE 'Ant%'进行测试并观察错误的输出。添加第二个GROUP BY子句和DISTINCT来解决由用户查询匹配的问题,这些查询匹配同一叶子的祖先的多个碎屑。

    SELECT
      DISTINCT GROUP_CONCAT(`breadCrumbCategories`.`CategoryName` SEPARATOR " > ")
    FROM `ebay_categories` AS `matchedCategory`
    INNER JOIN `tree_branches` AS `matchedCategoryLeaves` ON (`matchedCategoryLeaves`.`AncestorId` = `matchedCategory`.`categoryId`)
    INNER JOIN `tree_branches` AS `breadCrumbs` ON (`breadCrumbs`.`LeafId` = `matchedCategoryLeaves`.`LeafId`)
    INNER JOIN `ebay_categories` AS `breadCrumbCategories` ON (`breadCrumbCategories`.`CategoryId` = `breadCrumbs`.`ancestorId`)
    WHERE
      `matchedCategory`.`CategoryName` LIKE "An%"
    GROUP BY
      `breadCrumbs`.`LeafId`,
      `matchedCategory`.`CategoryId`
      ;