您可以在将特定行值提取到选定列时进行GROUP BY

时间:2013-05-31 01:12:02

标签: mysql

我有时会遇到父母/子女关系的情况,我想为每个父母选择所有孩子。

这是一个肤浅的例子。

SELECT parent.id as parent_id, child.id as child_id
FROM parent LEFT JOIN child ON parent.id = child.parent_id
ORDER BY parent.id

会产生

parent_id  child_id
1          20
1          21
2          33
2          67

我想做点什么

SELECT parent.id, child1.id as child1_id, child2.id as child2_id
FROM parent LEFT JOIN child ON parent.id = child.parent_id
GROUP BY parent.id

并且收益

parent_id  child1_id  child2_id
1          20         21
2          33         67

我正在尝试避免使用GROUP_CONCAT因为我想为每个孩子创建单独的列。

我意识到我可以加入孩子两次并过滤选择,但鉴于我的实际数据集,它可能会连续两次加入。而且,如果你可以用任意数量的孩子这样做,那真的很酷:

parent_id  child1_id  child2_id  child3_id
1          20         21         null
2          33         67         109
3          45         null       null

3 个答案:

答案 0 :(得分:2)

您正在寻找的内容称为PIVOT。 MySQL不支持PIVOT命令,但您可以使用MAX CASE来模拟它。

如果您知道孩子的数量或者您可以拥有最大数量,这将非常有用。

SELECT parent.id as parent_id, 
  MAX(CASE WHEN rn = 1 THEN child.id END) child1_id,
  MAX(CASE WHEN rn = 2 THEN child.id END) child2_id,
  MAX(CASE WHEN rn = 3 THEN child.id END) child3_id,
  MAX(CASE WHEN rn = 4 THEN child.id END) child4_id,
  MAX(CASE WHEN rn = 5 THEN child.id END) child5_id
FROM parent 
  LEFT JOIN (
    SELECT *, 
      @rn:=IF(@prevParent=parent_id,@rn+1,1) rn,
      @prevParent:=parent_id
    FROM child JOIN (SELECT @rn:=0,@prevParent:=0) t
) child ON parent.id = child.parent_id
GROUP BY parent.id
ORDER BY parent.id;

如果您不知道子项/潜在列的数量,则需要考虑创建动态SQL。

以下是一个例子:

SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(IF(rn = ', rn, ',child.id,NULL)) AS child_id', rn)
  ) INTO @sql
FROM parent 
  LEFT JOIN (
    SELECT *, 
      @rn:=IF(@prevParent=parent_id,@rn+1,1) rn,
      @prevParent:=parent_id
    FROM child JOIN (SELECT @rn:=0,@prevParent:=0) t
) child ON parent.id = child.parent_id
;

SET @sql = CONCAT('SELECT parent.id as parent_id, 
                          ', @sql, ' 
                  FROM parent 
                    LEFT JOIN (
                      SELECT *, 
                        @rn:=IF(@prevParent=parent_id,@rn+1,1) rn,
                        @prevParent:=parent_id
                      FROM child JOIN (SELECT @rn:=0,@prevParent:=0) t
                  ) child ON parent.id = child.parent_id
                  GROUP BY parent.id
                  ORDER BY parent.id;');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

答案 1 :(得分:1)

您可以在没有变量且没有动态SQL的情况下执行此操作。我们的想法是使用子查询和然后来获取有关列的信息。

让所有孩子:

SELECT parent.id, group_concat(child.id) as children, count(*) as numchildren
FROM parent LEFT JOIN
     child ON parent.id = child.parent_id
GROUP BY parent.id

现在使用substring_index()reverse()提取这些内容。这是一个复杂的表达式,它返回列表中的第n个值:

select parent_id,
       substring_index(children, ',', 1) as child1,
       reverse(substring_index(reverse(substring_index(children, ',', 2)), ',', 1)) as child2,
       reverse(substring_index(reverse(substring_index(children, ',', 3)), ',', 1)) as child3,
       reverse(substring_index(reverse(substring_index(children, ',', 4)), ',', 1)) as child4
from (SELECT parent.id, group_concat(child.id) as children, count(*) as numchildren
      FROM parent LEFT JOIN
           child ON parent.id = child.parent_id
      GROUP BY parent.id
     ) t

答案 2 :(得分:0)

您需要动态创建必要的SQ。

您所需查询的SQL需要静态知道要创建的列数以及要使用的列名。通过动态创建SQL查询,然后运行它,您可以实现所需的效果。