• Apache Doris 行列转换可以这样玩


    行列转换在做报表分析时还是经常会遇到的,今天就说一下如何实现行列转换吧。

    行列转换就是如下图所示两种展示形式的互相转换

    1. 行转列

    我们来看一个简单的例子,我们要把下面这个表的数据,转换成图二的样式

    image-20230914151818953.png

    要转换的结果数据展示

    image-20230914152642915.png

    先看看建表语句

    1. CREATE TABLE tb_score_01(
    2. id INT(11) NOT NULL,
    3. userid VARCHAR(20) NOT NULL COMMENT '用户id',
    4. subject VARCHAR(20) COMMENT '科目',
    5. score DOUBLE COMMENT '成绩'
    6. )
    7. DUPLICATE KEY(`id`)
    8. COMMENT 'OLAP'
    9. DISTRIBUTED BY HASH(`id`) BUCKETS 1
    10. PROPERTIES (
    11. "replication_allocation" = "tag.location.default: 1",
    12. "in_memory" = "false",
    13. "storage_format" = "V2",
    14. "light_schema_change" = "true",
    15. "disable_auto_compaction" = "false"
    16. );
    17. INSERT INTO tb_score_01 VALUES (1,'001','语文',90);
    18. INSERT INTO tb_score_01 VALUES (2,'001','数学',92);
    19. INSERT INTO tb_score_01 VALUES (3,'001','英语',80);
    20. INSERT INTO tb_score_01 VALUES (4,'002','语文',88);
    21. INSERT INTO tb_score_01 VALUES (5,'002','数学',90);
    22. INSERT INTO tb_score_01 VALUES (6,'002','英语',75.5);
    23. INSERT INTO tb_score_01 VALUES (7,'003','语文',70);
    24. INSERT INTO tb_score_01 VALUES (8,'003','数学',85);
    25. INSERT INTO tb_score_01 VALUES (9,'003','英语',90);
    26. INSERT INTO tb_score_01 VALUES (10,'003','政治',82);

    传统的做法我们大概是这样实现,一般是通过 case when 语句

    1. SELECT userid,
    2. SUM(CASE `subject` WHEN '语文' THEN score ELSE 0 END) as '语文',
    3. SUM(CASE `subject` WHEN '数学' THEN score ELSE 0 END) as '数学',
    4. SUM(CASE `subject` WHEN '英语' THEN score ELSE 0 END) as '英语',
    5. SUM(CASE `subject` WHEN '政治' THEN score ELSE 0 END) as '政治'
    6. FROM tb_score
    7. GROUP BY userid;
    8. 或者
    9. SELECT userid,
    10. SUM(IF(`subject`='语文',score,0)) as '语文',
    11. SUM(IF(`subject`='数学',score,0)) as '数学',
    12. SUM(IF(`subject`='英语',score,0)) as '英语',
    13. SUM(IF(`subject`='政治',score,0)) as '政治'
    14. FROM tb_score
    15. GROUP BY userid;

    我们来看看 Doris 怎么实现这个行转列呢,有没有更简单、性能更好的一种方式

    1. 我们是不是可以首先将这个科目、成绩组成一个Map

    2. 然后在外层对这个 Map 进行遍历展开

    3. 从而完成这样一个行列转换呢

    我们来看看实现

    1. select
    2. userid,
    3. IFNULL(map['语文'],0) as '语文',
    4. IFNULL(map['英语'],0) as '英语',
    5. IFNULL(map['数学'],0) as '数学',
    6. IFNULL(map['政治'],0) as '政治'
    7. from (
    8. select userid ,map_agg(subject,score) as map from tb_score group by userid
    9. ) t ;

    这样实现上性能更好,我们来看一下效果

    1. select
    2. -> userid,
    3. -> IFNULL(map['语文'],0) as '语文',
    4. -> IFNULL(map['英语'],0) as '英语',
    5. -> IFNULL(map['数学'],0) as '数学',
    6. -> IFNULL(map['政治'],0) as '政治'
    7. -> from (
    8. -> select userid ,map_agg(subject,score) as map from tb_score group by userid
    9. -> ) t ;
    10. +--------+--------+--------+--------+--------+
    11. | userid | 语文 | 英语 | 数学 | 政治 |
    12. +--------+--------+--------+--------+--------+
    13. | 001 | 90 | 80 | 92 | 0 |
    14. | 002 | 88 | 75.5 | 90 | 0 |
    15. | 003 | 70 | 90 | 85 | 82 |
    16. +--------+--------+--------+--------+--------+
    17. 3 rows in set (0.02 sec)

    2. 列转行

    实际使用中我们还有很多场景要把数据冲列转成行,下面我们来看一个例子,这个例子中每行是一个学生的,语文、数学、英语、政治的成绩,

    image-20230914152642915.png

    我们想转换成每门成绩都是独立的一行,转出的效果如下:

    image-20230914152846996.png

    我们来看看一个宽表转成高表我们之前的是怎么实现,一般我们是通过union all的方式,每科我们都是一个单独的SQL语句,然后将这些SQL Unoin all 在一起得到我们想要的结果。

    1. SELECT userid,'语文' AS course,cn_score AS score FROM tb_score1
    2. UNION ALL
    3. SELECT userid,'数学' AS course,math_score AS score FROM tb_score1
    4. UNION ALL
    5. SELECT userid,'英语' AS course,en_score AS score FROM tb_score1
    6. UNION ALL
    7. SELECT userid,'政治' AS course,po_score AS score FROM tb_score1
    8. ORDER BY userid;

    这样做的缺点:

    1. SQL 冗余

    2. 大量的union all 也会带来性能问题

    我们来看看 Doris 怎么实现,首先 Doris 提供了 Lateral view,其实就是用来和像类似explode这种UDTF函数联用的,lateral view会将 UDTF 生成的结果放到一个虚拟表中,然后这个虚拟表会和输入行进行 join来达到连接 UDTF 外的 select 字段的目的

    还是以上面的例子来看,Doris我怎么对这个宽表转成高表,实现就是借助Lateral view

    1. CREATE TABLE `tb_score1` (
    2. `id` int(11) NOT NULL,
    3. `userid` varchar(20) NOT NULL COMMENT '用户id',
    4. `cn_score` double NULL COMMENT '语文成绩',
    5. `math_score` double NULL COMMENT '数学成绩',
    6. `en_score` double NULL COMMENT '英语成绩',
    7. `po_score` double NULL COMMENT '政治成绩'
    8. ) ENGINE=OLAP
    9. UNIQUE KEY(`id`)
    10. COMMENT 'OLAP'
    11. DISTRIBUTED BY HASH(`id`) BUCKETS 1
    12. PROPERTIES (
    13. "replication_allocation" = "tag.location.default: 1",
    14. "is_being_synced" = "false",
    15. "storage_format" = "V2",
    16. "light_schema_change" = "true",
    17. "disable_auto_compaction" = "false",
    18. "enable_single_replica_compaction" = "false"
    19. );;
    20. INSERT INTO `tb_score1` (`id`, `userid`, `cn_score`, `math_score`, `en_score`, `po_score`) VALUES (1, '001', 90, 92, 80, 0);
    21. INSERT INTO `tb_score1` (`id`, `userid`, `cn_score`, `math_score`, `en_score`, `po_score`) VALUES (2, '002', 88, 90, 75.5, 0);
    22. INSERT INTO `tb_score1` (`id`, `userid`, `cn_score`, `math_score`, `en_score`, `po_score`) VALUES (3, '003', 70, 85, 90, 82);
    1. 首先我借助Lateral view 形成一个 UserID、客户成绩组成一个字符(使用逗号连接),达到下面的效果
    1. +--------+--------------------+
    2. | userid | arr |
    3. +--------+--------------------+
    4. | 001 | ["语文", "90"] |
    5. | 001 | ["数学", "92"] |
    6. | 001 | ["英语", "80"] |
    7. | 001 | ["政治", "0"] |
    8. | 002 | ["语文", "88"] |
    9. | 002 | ["数学", "90"] |
    10. | 002 | ["英语", "75.5"] |
    11. | 002 | ["政治", "0"] |
    12. | 003 | ["语文", "70"] |
    13. | 003 | ["数学", "85"] |
    14. | 003 | ["英语", "90"] |
    15. | 003 | ["政治", "82"] |
    16. +--------+--------------------+
    17. 12 rows in set (0.02 sec)
    1. 然后对这个上面的 arr 字符串,借助于 Doris 提供的 SPLIT_BY_STRING 函数完成字符串转数组的动作

    2. 最后遍历数组

    3. 完成列转行的效果

    1. SELECT
    2. userid,
    3. element_at ( arr, 1 ) AS SUBJECT,
    4. element_at ( arr, 2 ) AS score
    5. FROM
    6. (
    7. SELECT
    8. userid,
    9. SPLIT_BY_STRING ( sub, ',' ) arr
    10. FROM
    11. (
    12. SELECT
    13. userid,
    14. array (
    15. concat( '语文', ',', cn_score ),
    16. concat( '数学', ',', math_score ),
    17. concat( '英语', ',', en_score ),
    18. concat( '政治', ',', po_score )) AS scores
    19. FROM
    20. tb_score1
    21. ) t LATERAL VIEW explode ( scores ) tbl1 AS sub
    22. ) aaa

    最后的效果如下:

    1. SELECT
    2. -> userid,
    3. -> element_at ( arr, 1 ) AS SUBJECT,
    4. -> element_at ( arr, 2 ) AS score
    5. -> FROM
    6. -> (
    7. -> SELECT
    8. -> userid,
    9. -> SPLIT_BY_STRING ( sub, ',' ) arr
    10. -> FROM
    11. -> (
    12. -> SELECT
    13. -> userid,
    14. -> array (
    15. -> concat( '语文', ',', cn_score ),
    16. -> concat( '数学', ',', math_score ),
    17. -> concat( '英语', ',', en_score ),
    18. -> concat( '政治', ',', po_score )) AS scores
    19. -> FROM
    20. -> tb_score1
    21. -> ) t LATERAL VIEW explode ( scores ) tbl1 AS sub
    22. -> ) aaa;
    23. +--------+---------+-------+
    24. | userid | SUBJECT | score |
    25. +--------+---------+-------+
    26. | 001 | 语文 | 90 |
    27. | 001 | 数学 | 92 |
    28. | 001 | 英语 | 80 |
    29. | 001 | 政治 | 0 |
    30. | 002 | 语文 | 88 |
    31. | 002 | 数学 | 90 |
    32. | 002 | 英语 | 75.5 |
    33. | 002 | 政治 | 0 |
    34. | 003 | 语文 | 70 |
    35. | 003 | 数学 | 85 |
    36. | 003 | 英语 | 90 |
    37. | 003 | 政治 | 82 |
    38. +--------+---------+-------+
    39. 12 rows in set (0.02 sec)

    日记本

  • 相关阅读:
    Oracle数据库体系结构(四)_存储参数
    python学习笔记——条件、循环和异常
    Spring Cloud框架(原生Hoxton版本与Spring Cloud Alibaba)基础入门篇 ---- 搭建环境
    Vue安装过程及环境配置
    oracle练习03
    服务器的架构有哪些
    Vue 里,多级菜单要如何设计才显得专业?
    IPKISS Tutorials 3------绘制矩形版图
    如果线上遇到了OOM,该如何解决?
    golang 版本升级
  • 原文地址:https://blog.csdn.net/hf200012/article/details/133305565