MySQL:在一个Statement上连接多个表

时间:2014-09-03 12:48:44

标签: mysql sql join categories hierarchy

我有以下DB-Structure / hierarchy:

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:一个组(param:$selected_group

现在我想显示符合以下选项的所有产品

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

AND

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 ..等等。但没有成功。

非常感谢

编辑:我上面使用的声明错误,所以不要使用它!

2 个答案:

答案 0 :(得分: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保存在应用程序之前生成这些代码。当我们有大量记录时,它将比递归查询或过程更有效。我认为这是一种很好的非规范化方法。

使用这种方法,您希望加入声明可以是:

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,我还没有测试过。但我认为这与前两种选择相似。

需要在treenode表中添加左数和右数以包含所有后代'他们之间的ids。

答案 1 :(得分:0)

这在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$$

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