MySQL在单个父子表中增加分层数据

时间:2014-01-07 15:02:37

标签: mysql hierarchical-data

我正在研究一个MySQL数据库包含两个表的项目;人和百分比。

人员表:

+----+------+--------+
| ID | Name | Parent |
+----+------+--------+
|  1 | A    |      0 |
|  2 |  B   |      1 |
|  3 |   C  |      2 |
|  4 |    D |      3 |
|  5 |  E   |      1 |
|  6 | F    |      0 |
+----+------+--------+

百分比表:

+----+------------+
| ID | Percentage |
+----+------------+
|  1 | 70%        |
|  2 | 60%        |
|  3 | 10%        |
|  4 | 5%         |
|  5 | 40%        |
|  6 | 30%        |
+----+------------+

我正在寻找的查询结果如下:

+----+------------+----------------+--------+
| ID | Percentage |  Calculation   | Actual |
+----+------------+----------------+--------+
|  1 |         70 | 70%            | 70.00% |
|  2 |         60 | 70%*60%        | 42.00% |
|  3 |         10 | 70%*60%*10%    | 4.20%  |
|  4 |          5 | 70%*60%*10%*5% | 0.21%  |
|  5 |         40 | 70%*40%        | 28.00% |
|  6 |         30 | 30%            | 30.00% |
+----+------------+----------------+--------+

“计算”列仅用于详细说明。是否有任何MySQL技术可用于实现此分层查询?即使百分比表可能包含同一个人的多个条目(百分比)?

5 个答案:

答案 0 :(得分:2)

解决方案是利用以下链接中描述的功能进行层次化查询:

不是制作PATH,而是想要计算乘法。

解决方案脚本

将其直接复制并粘贴到mysql控制台中。我在工作台上没有太多运气。此外,通过将hierarchy_sys_connect_by_path_percentagehierarchy_sys_connect_by_path_percentage_result组合到一个存储过程中,可以进一步优化这一点。不幸的是,对于巨型数据集来说,这可能相当慢。

设置表和数据

drop table people;
drop table percentages;

create table people
(
  id  int,
  name varchar(10),
  parent int
);

create table percentages
(
   id int,
   percentage float
);

insert into people values(1,' A    ',0);
insert into people values(2,'  B   ',1);
insert into people values(3,'   C  ',2);
insert into people values(4,'    D ',3);
insert into people values(5,'  E   ',1);
insert into people values(6,' F    ',0);

insert into percentages values(1,0.70);
insert into percentages values(2,0.60);
insert into percentages values(3,0.10);
insert into percentages values(4,0.5);
insert into percentages values(5,0.40);
insert into percentages values(6,0.30);

DELIMITER $$

DROP  FUNCTION  IF  EXISTS  `hierarchy_sys_connect_by_path_percentage`$$

CREATE FUNCTION hierarchy_sys_connect_by_path_percentage(
                                       delimiter TEXT, 
                                       node INT) 
                                       RETURNS TEXT
    NOT DETERMINISTIC
    READS SQL DATA
BEGIN
     DECLARE _path TEXT;
     DECLARE _id INT;
     DECLARE _percentage FLOAT;
     DECLARE EXIT HANDLER FOR NOT FOUND RETURN _path;
     SET _id = COALESCE(node, @id);

        SELECT  Percentage
              INTO    _path
         FROM    percentages
         WHERE   id = _id;

     LOOP
              SELECT  parent
              INTO    _id
         FROM    people
         WHERE   id = _id
                    AND COALESCE(id <> @start_with, TRUE);

        SELECT  Percentage
              INTO    _percentage
         FROM    percentages
         WHERE   id = _id;

        SET _path = CONCAT( _percentage , delimiter, _path);
    END LOOP;
END $$


DROP  FUNCTION  IF  EXISTS  `hierarchy_sys_connect_by_path_percentage_result`$$

CREATE FUNCTION hierarchy_sys_connect_by_path_percentage_result(
                                       node INT) 
                                       RETURNS FLOAT
    NOT DETERMINISTIC
    READS SQL DATA
BEGIN
     DECLARE _path TEXT;
     DECLARE _id INT;
     DECLARE _percentage FLOAT;
     DECLARE EXIT HANDLER FOR NOT FOUND RETURN _path;
     SET _id = COALESCE(node, @id);

        SELECT  Percentage
              INTO    _path
         FROM    percentages
         WHERE   id = _id;

     LOOP
              SELECT  parent
              INTO    _id
         FROM    people
         WHERE   id = _id
                    AND COALESCE(id <> @start_with, TRUE);

        SELECT  Percentage
              INTO    _percentage
         FROM    percentages
         WHERE   id = _id;

        SET _path = _percentage *  _path;
    END LOOP;
END $$

DELIMITER ;

查询

SELECT  hi.id AS ID,
        p.Percentage,
        hierarchy_sys_connect_by_path_percentage('*', hi.id) AS Calculation,
        hierarchy_sys_connect_by_path_percentage_result(hi.id) AS Actual        
FROM    people hi
JOIN    percentages p
ON      hi.id = p.id;

<强>结果

+------+------------+-----------------+--------------------+
| ID   | Percentage | Calculation     | Actual             |
+------+------------+-----------------+--------------------+
|    1 |        0.7 | 0.7             |  0.699999988079071 |
|    2 |        0.6 | 0.7*0.6         |  0.419999986886978 |
|    3 |        0.1 | 0.7*0.6*0.1     | 0.0419999994337559 |
|    4 |        0.5 | 0.7*0.6*0.1*0.5 | 0.0210000015795231 |
|    5 |        0.4 | 0.7*0.4         |  0.280000001192093 |
|    6 |        0.3 | 0.3             |  0.300000011920929 |
+------+------------+-----------------+--------------------+

格式化数字是微不足道的,所以我留给你... 更重要的是优化以减少对数据库的调用。

答案 1 :(得分:1)

步骤1 - 创建一个MySQL函数,以逗号分隔的TEXT列返回族树:

DELIMITER //

CREATE FUNCTION fnFamilyTree ( id INT ) RETURNS TEXT
BEGIN
   SET @tree = id;
   SET @qid = id;
   WHILE (@qid > 0) DO
      SELECT IFNULL(p.parent,-1)
        INTO @qid
        FROM people p
       WHERE p.id = @qid LIMIT 1;
      IF ( @qid > 0 ) THEN
        SET @tree = CONCAT(@tree,',',@qid);
      END IF;
   END WHILE;
   RETURN @tree;
END
//

DELIMITER ;

然后使用以下SQL检索结果:

SELECT ppl.id
      ,ppl.percentage
      ,GROUP_CONCAT(pct.percentage SEPARATOR '*') as Calculations
      ,EXP(SUM(LOG(pct.percentage)))              as Actual
  FROM (SELECT p1.id
              ,p2.percentage
              ,fnFamilyTree( p1.id ) as FamilyTree
          FROM people      p1
          JOIN percentages p2
            ON p2.id = p1.id
       ) ppl
  JOIN percentages pct
    ON FIND_IN_SET( pct.id, ppl.FamilyTree ) > 0
 GROUP BY ppl.id
         ,ppl.percentage
;

http://sqlfiddle.com/#!2/9da5b/12

的SQLFiddle

结果:

+------+----------------+-----------------+----------------+
| ID   | Percentage     | Calculations    | Actual         |
+------+----------------+-----------------+----------------+
| 1    | 0.699999988079 | 0.7             | 0.699999988079 |
| 2    | 0.600000023842 | 0.7*0.6         | 0.420000009537 |
| 3    | 0.10000000149  | 0.7*0.6*0.1     | 0.04200000158  |
| 4    | 0.5            | 0.1*0.5*0.7*0.6 | 0.02100000079  |
| 5    | 0.40000000596  | 0.4*0.7         | 0.279999999404 |
| 6    | 0.300000011921 | 0.3             | 0.300000011921 |
+------+----------------+-----------------+----------------+

答案 2 :(得分:0)

MySQL是 Relational DBS。您的要求需要Graph database

但是,如果你留在MySQL,有一些方法可以添加一些图形功能。其中之一是Nested Sets的概念。但我不建议,因为它增加了很多复杂性。

答案 3 :(得分:0)

SELECT a.id
     , ROUND(pa.percentage/100
     * COALESCE(pb.percentage/100,1)
     * COALESCE(pc.percentage/100,1)
     * COALESCE(pd.percentage/100,1) 
     * 100,2) x
  FROM people a 
  LEFT 
  JOIN people b 
    ON b.id = a.parent 
  LEFT 
  JOIN people c 
    ON c.id = b.parent 
  LEFT 
  JOIN people d 
    ON d.id = c.parent
  LEFT
  JOIN percentages pa
    ON pa.id = a.id
  LEFT
  JOIN percentages pb
    ON pb.id = b.id
  LEFT
  JOIN percentages pc
    ON pc.id = c.id
  LEFT
  JOIN percentages pd
    ON pd.id = d.id
;

答案 4 :(得分:-1)

考虑切换到Postgres9,它支持recursive queries

WITH RECURSIVE recp AS (
SELECT p.id, p.name, p.parent
, array[p.id] AS anti_loop
, array[pr.percentage ] AS percentages
, pr.percentage AS final_pr
FROM people p
JOIN percentages pr ON pr.id = p.id
WHERE parent = 0
UNION ALL
SELECT ptree.id, ptree.name, ptree.parent
, recp.anti_loop || ptree.id
, recp.percentages || pr.percentage
, recp.final_pr * pr.percentage
FROM people ptree
JOIN percentages pr ON pr.id = ptree.id
JOIN recp ON recp.id = ptree.parent AND ptree.id != ALL(recp.anti_loop)
)
SELECT id, name
, array_to_string(anti_loop, ' <- ') AS path
, array_to_string(percentages::numeric(10,2)[], ' * ') AS percentages_str
, final_pr
FROM recp
ORDER BY anti_loop

查看sqlFiddle演示