【问题标题】:MySQL: join many tables on one StatementMySQL:在一个语句上连接多个表
【发布时间】:2014-09-03 12:48:44
【问题描述】:

我有以下数据库结构/层次结构:

product_type

id,name, ....

产品

id,parent_id, name, ...

parent_id:是product_type id

treeNode

id, parent_id, name, type

它是一个树层次结构(根有 n 个子节点) 层次结构的数量是未知

col type 的值为 "CATEGORY""GROUP", 这意味着,我有 2 棵树:

类别:

   TreeNode 1
         sub 1.1
                sub.1.1.1
                sub.1.1.2
                  ....
         sub 1.2
                sub.1.2.1
                sub.1.2.2
                  ....
   TreeNode 2
         sub 2.1
                sub.2.1.1
                sub.2.1.2
                  ....
         sub 2.2
                sub.2.2.1
                sub.2.2.2
                  ....

组:

   TreeNode 1
         sub 1.1
                sub.1.1.1
                sub.1.1.2
                  ....
         sub 1.2
                sub.1.2.1
                sub.1.2.2
                  ....
   TreeNode 2
         sub 2.1
                sub.2.1.1
                sub.2.1.2
                  ....
         sub 2.2
                sub.2.2.1
                sub.2.2.2
                  ....

linked_treeNode:

product_id, treeNode_id

现在让我们说,用户选择:

1:产品类型(参数:$selected_type

2:一个类别(参数:$selected_cat

3:一个组(参数:$selected_group

现在我想显示满足这些选择的所有产品

1-) 链接到选定的类别或其子类别

2-) 链接到选定的组或其子组

3-) 链接到选定的产品类型

MySQL 语句(1 条语句)是什么?

我试过这个:

SELECT P.* FROM 
product P, treeNode C, treeNode G, linked_TreeNode LC
WHERE 
p.parent_id='$selected_type' 
AND
( 
       C.type='CATEGORY' 
       AND 
       C.parent_id='$selected_cat' 
       AND 
       P.id=LC.product_id 
       AND 
       (LC.treeNode_id=C.id OR LC.treeNode_id='$selected_cat') 
)
AND
( 
       G.type='GROUP' 
       AND 
       G.parent_id='$selected_group' 
       AND 
       P.id=LC.product_id 
       AND 
       (LC.treeNode_id=G.id OR LC.treeNode_id='$selected_group') 
)
;

但我总是得到 0 结果!

我尝试了许多其他语句(更改),包括 JOINS ..etc。但没有成功。

非常感谢

编辑:我上面使用的语句是错误的,所以不要使用它!

【问题讨论】:

  • 尝试编辑您的问题并输入使用join的查询。
  • 这是同一个问题吗? - stackoverflow.com/questions/20215744/…
  • @KenanZahirovic,不,不完全是,我想要链接到所有选择的产品,而不是树节点本身。我的 TreeNodes 有一个额外的 col 命名类型!
  • @GordonLinoff 是对的,使用连接查询
  • @GordonLinoff,这实际上是我的问题,怎么样?

标签: mysql sql join categories hierarchy


【解决方案1】:

如何在MySql中通过递归查询从树节点获取所有后代?

对MySql来说确实是个问题,也是这个问题的关键点,但是你还是有一些选择的。

假设你有这样的样本数据,没有你的样本多但足以证明:

create table treeNode(
id int, parent_id  int,  name varchar(10), type varchar(10),level int);
insert into treeNode 
(id, parent_id, name, type, level) values 
( 1,  0,  'C1    ', 'CATEGORY', 1),
( 2,  1,  'C1.1  ', 'CATEGORY', 2),
( 3,  2,  'C1.1.1', 'CATEGORY', 3),
( 4,  1,  'C1.2  ', 'CATEGORY', 2),
( 5,  4,  'C1.2.1', 'CATEGORY', 3),
( 3,  8,  'G1.1.1',    'GROUP', 3),
( 4,  9,  'G1.2  ',    'GROUP', 2),
( 5,  4,  'G1.2.1',    'GROUP', 3),
( 8,  9,  'G1.1  ',    'GROUP', 2),
( 9,  0,  'G1    ',    'GROUP', 1);

第一选择:关卡代码

类似于treeNode 表中name 列的示例数据。 (我不知道英文怎么说,评论我level code的正确表达方式。)

要获取C1G1 的所有后代,可以这样简单:

select * from treeNode where type = 'CATEGORY' and name like 'C1%' ;
select * from treeNode where type = 'GROUP' and name like 'G1%' ;

我非常喜欢这种方式,甚至需要我们在treeNode保存到应用程序之前生成这些代码。当我们有大量记录时,它会比递归查询或过程更有效。我认为这是一种很好的非规范化方法。

使用这种方法,您希望 with join语句可能是:

SELECT distinct p.* --if there is only one tree node for a product, distinct is not needed
FROM product p
JOIN product_type pt
     ON pt.id= p.parent_id -- to get product type of a product
JOIN linked_TreeNode LC
     ON LC.product_id= p.id -- to get tree_nodes related to a product
JOIN (select * from treeNode where type = 'CATEGORY' and name like 'C1%' ) C --may replace C1% to concat('$selected_cat_name','%')
     ON LC.treeNode_id = C.id
JOIN (select * from treeNode where type = 'GROUP' and name like 'G1%' ) G --may replace G1% to concat('$selected_group_name','%')
     ON LC.treeNode_id = G.id
WHERE pt.name = '$selected_type'  -- filter selected product type, assuming using product.name, if using product.parent_id, can save one join by pt like your original sql

甜蜜,不是吗?

二选一:级数

向 treeNode 表追加一个级别列,如 DDL 中所示。

级别编号比应用程序中的级别代码更容易维护。

用级别号来获取C1G1的所有后代需要这样的小技巧:

SELECT id, parent_id, name, type, @pv:=concat(@pv,',',id) as link_ids 
  FROM (select * from treeNode where type = 'CATEGORY' order by level) as t
  JOIN (select @pv:='1')tmp
 WHERE find_in_set(parent_id,@pv)
    OR find_in_set(id,@pv);
 -- get all descendants of `C1`

SELECT id, parent_id, name, type, @pv:=concat(@pv,',',id) as link_ids 
  FROM (select * from treeNode where type = 'GROUP' order by level) as t
  JOIN (select @pv:=',9,')tmp
 WHERE find_in_set(parent_id,@pv)
    OR find_in_set(id,@pv) ;

这种方法比第一种方法慢,但仍然比递归查询快。

省略了问题的完整 sql。只需要将C和G的这两个子查询替换成上面的两个查询即可。

注意:

类似的方法还有很多,比如herehere,甚至here。除非按级别编号或级别代码排序,否则它们将不起作用。您可以通过将 order by level 更改为 order by id 来测试此 SqlFiddle 中的最后一个查询以查看差异。

另一种选择:嵌套集模型

请参考这个blog,我还没测试。但我认为它类似于最后两个选择。

它需要你在树节点表中添加一个左数和一个右数,以将所有后代的 id 包含在它们之间。

【讨论】:

  • 感谢您的建议。我喜欢“嵌套集模型”,但“按预期”我必须修改我的树结构。
【解决方案2】:

这在 MySQL 中是不可行的,因为它缺少您需要的功能:递归查询。

Oracle 可以使用 START WITH ... CONNECT BY 语句来做到这一点。

您在过程中对表进行递归,并将结果写入临时表。可以在同一个会话中查询该表。

类似:

CREATE PROCEDURE products_by_cat_and_grp(typ INT, cat INT, grp INT)
BEGIN

-- create temporary table which we query later on
CREATE TEMPORARY TABLE tmp_products LIKE product;
ALTER TABLE tmp_products
ADD cat_id INT
, ADD grp_id INT;

-- first insert all products of the category and group
INSERT INTO  tmp_products
SELECT P.*, cat.id, grp.id
FROM linked_TreeNode lc 
JOIN product prd
  ON lc.product_id = prd.id
JOIN treeNode cat
  ON lc.treeNode_id = cat.id
JOIN treeNode grp
  ON lc.treeNode_id = grp.id
WHERE prd.parent_id = typ
  AND cat.id = cat
  AND grp.id = grp;

-- now we iterate over subcategories until there is nothing left
SET @rownum = 1;

WHILE @rownum > 0 DO
  CREATE TEMPORARY TABLE tmp_parents
  AS SELECT DISTINCT id, cat_id AS parent_id
  FROM tmp_products
  UNION SELECT DISTINCT id, grp_id AS parent_id
  FROM tmp_products;

  INSERT INTO  tmp_products
  SELECT P.*, cat.id, grp.id
  FROM linked_TreeNode lc 
  JOIN treeNode tn
  JOIN product prd
    ON lc.product_id = prd.id
  JOIN treeNode cat
    ON lc.treeNode_id = cat.id
  JOIN treeNode grp
    ON lc.treeNode_id = grp.id
  JOIN tmp_parents par
    ON (par.parent_id = cat.parent_id
    OR par.parent_id = grp.parent_id)
    AND par.id <> lc.product_id
  WHERE prd.parent_id = typ;
  -- see how many rows were inserted. If this becomes zero, the recursion is complete
  SET @rownum = ROW_COUNT();
END WHILE;

SELECT * FROM tmp_products;
END$$

这没有经过调整或测试,我也不推荐它,因为查询可能需要很长时间才能返回。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 2016-06-21
    • 1970-01-01
    • 2011-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多