SparkSQL-优雅行列转换

embedded/2024/9/25 13:17:30/

行列转换是数据处理与分析中的关键操作,它能够将数据的结构从行转为列,或从列转为行。这种转换不仅简化了复杂的数据展示,还提升了数据分析的效率。在业务场景中,行列转换常用于报表生成、数据透视和多维度数据分析,通过更直观的方式呈现数据,帮助管理者快速获取关键信息。此外,它还能有效减少数据冗余,优化查询性能,满足灵活多变的业务需求。无论是在财务报表、销售分析,还是市场趋势分析中,行列转换都是不可或缺的工具。

本文会基于 SparkSQL 3.5.x 给出常用的行列转换方式,但本文的重点是介绍pivotunpivot子句在行列转换场景的应用,其中细节、优雅程度交由开发者自己选择

一、数据准备

以下是城市各年GDP 数据的表结构和测试数据,用于后续演示行列转换

sql">create table city_gdp
(city string comment '城市名',year int comment '年份',gdp  double comment '单位:亿'
) comment '城市 gdp'stored as parquet;insert into city_gdp (city, year, gdp)
values ('北京', 2018, 30320.00),('北京', 2019, 35370.00),('北京', 2020, 36100.00),('上海', 2018, 32600.00),('上海', 2019, 38160.00),('上海', 2020, 38700.00),('广州', 2018, 23000.00),('广州', 2019, 23628.00),('广州', 2020, 25019.00);

二、行列转换

2.1 传统方式

2.1.1 行转列

统计各城市 2018-2020 之间的 GDP,不同年份作为单独列显示。下面是 sql 实现

sql">select city,max(case when year = 2018 then gdp end) as `2018`,max(case when year = 2019 then gdp end) as `2019`,max(case when year = 2020 then gdp end) as `2020`
from city_gdp
group by city;+-------+----------+----------+----------+
| city  |   2018   |   2019   |   2020   |
+-------+----------+----------+----------+
| 上海    | 32600.0  | 38160.0  | 38700.0  |
| 广州    | 23000.0  | 23628.0  | 25019.0  |
| 北京    | 30320.0  | 35370.0  | 36100.0  |
+-------+----------+----------+----------+

2.1.2 列转行

为了更好的演示,将 2.1.1 的结果存储到临时表

sql">create table tmp_city_gdp stored as parquet as
select city,max(case when year = 2018 then gdp end) as `2018`,max(case when year = 2019 then gdp end) as `2019`,max(case when year = 2020 then gdp end) as `2020`
from city_gdp
group by city;

基于 2.1.1 的结果,统计各城市 2018-2020 之间的 GDP,不同年份作为单独行显示。下面是 sql 实现

sql">select city, '2018' as year, `2018` as gdp
from tmp_city_gdp
union all
select city, '2019', `2019`
from tmp_city_gdp
union all
select city, '2020', `2020`
from tmp_city_gdp;+-------+-------+----------+
| city  | year  |   gdp    |
+-------+-------+----------+
| 上海    | 2018  | 32600.0  |
| 北京    | 2018  | 30320.0  |
| 广州    | 2018  | 23000.0  |
| 上海    | 2019  | 38160.0  |
| 北京    | 2019  | 35370.0  |
| 广州    | 2019  | 23628.0  |
| 上海    | 2020  | 38700.0  |
| 北京    | 2020  | 36100.0  |
| 广州    | 2020  | 25019.0  |
+-------+-------+----------+

2.2 pivot 和 unpivot

2.2.1 行转列

pivot的标准语法如下

sql">SELECT [columns]
FROM (SELECT [columns]FROM table_name
) 
PIVOT (aggregate_function([column]) FOR [column_to_pivot] IN ([pivot_values])
)

pivot子句是可以完全替代case when

sql">select *
from (select city, year, gdpfrom city_gdp)
pivot (sum(gdp) for year in (2018, 2019, 2020)
);+-------+----------+----------+----------+
| city  |   2018   |   2019   |   2020   |
+-------+----------+----------+----------+
| 上海    | 32600.0  | 38160.0  | 38700.0  |
| 北京    | 30320.0  | 35370.0  | 36100.0  |
| 广州    | 23000.0  | 23628.0  | 25019.0  |
+-------+----------+----------+----------+

2.2.2 列转行

unpivot的标准语法如下

sql">SELECT [columns]
FROM table_name 
UNPIVOT [ { INCLUDE | EXCLUDE } NULLS ](column_value_unpivot FOR name_column IN (unpivot_column [as alias])
)

Tips:

  1. unpivot 子句会产生新的字段,列名需要自定义
  2. column_value_unpivot 定义 unpivot 度量值字段名
  3. name_column 定义 unpivot 维度值字段名
  4. unpivot_column 限定 unpivot 需要转成行的列,从 table_name 中选取且作为 name_colume 的取值
  5. unpivot 列转行时默认剔除 null 值,如需要体现 null 值所在的 unpivot_column 需要使用 unpivot include nulls

unpivot子句是可以完全替代union all

sql">select *
from tmp_city_gdp
unpivot include nulls (gdp for year in (`2018`, `2019`, `2020`)
);+-------+-------+----------+
| city  | year  |   gdp    |
+-------+-------+----------+
| 上海    | 2018  | 32600.0  |
| 上海    | 2019  | 38160.0  |
| 上海    | 2020  | 38700.0  |
| 北京    | 2018  | 30320.0  |
| 北京    | 2019  | 35370.0  |
| 北京    | 2020  | 36100.0  |
| 广州    | 2018  | 23000.0  |
| 广州    | 2019  | 23628.0  |
| 广州    | 2020  | 25019.0  |
+-------+-------+----------+

三、总结

使用pivotunpivot相对比传统的case whenunion all主要的优势在代码维护和可读性上,在性能上的提升主要集中在数据扫描、减少不必要的计算。


http://www.ppmy.cn/embedded/113655.html

相关文章

25. MyBatis中的RowBounds是什么?如何实现内存分页?

是 MyBatis 提供的一种用于结果集分页的功能,主要通过内存分页的方式实现。它通过在查询时传递分页参数,限制返回的结果集的大小。RowBounds 并不依赖于数据库层的分页功能,而是通过 MyBatis 在内存中对结果集进行截取,从而实现分…

java开发中间件学习记录(持续更新中~)

1 Redis 2JVM 3 java基础底层 4Mysql 5 spring 6 微服务 7.......(持续更新) One:Redis篇 1:Redis 1.穿透 1.1缓存穿透 1.1.1布隆过滤器 1.2缓存击穿 2:击穿 1.3:缓存雪崩 1.4:双写一致 1.5.持久化(RDB,AOF) 1.6…

搭建内网文件服务器(FTP),以及实现内网Gitee

一、实现windows搭建FTP,实现文件共享和管理 具体步骤: 1.打开控制面板,搜索功能 2.打开这几个配置 3.打开IIS,添加FTP站点即可 二、实现内网Gitee 参考博客: Gitblit服务器搭建及Git使用-CSDN博客 jdk1.8.0的安…

Leetcode 第 414 场周赛题解

Leetcode 第 414 场周赛题解 Leetcode 第 414 场周赛题解题目1:3280. 将日期转换为二进制表示思路代码复杂度分析 题目2:3281. 范围内整数的最大得分思路代码复杂度分析 题目3:3282. 到达数组末尾的最大得分思路代码复杂度分析 题目4&#xf…

金蝶SHR,在列表对某个金额字段汇总展示的需求

接到这个需求的初衷是因为二开了一个内推费用计算,其中有一列本次发放奖金,希望能做个汇总,以便转薪酬后去汇总核对 刚开始也翻看了薪酬查询的表格底部汇总,捣鼓了半天搞不出来,恕我无能。。。 后面换种方式&#xf…

【Linux】查看操作系统开机时初始化的驱动模块列表的一个方法

这个方法是摸索出来的,也不一定对: 1、驱动层module_init(module_init_function)作为模块初始化,并且提供模块内部初始化的函数名; 2、找到所有驱动目录drivers下所有module_init(module_init_function),在内核6.9.0…

Agile Modbus STM32裸机移植 从机使用

本教程手把手教你实现Agile Modbus,照抄就能成。 并且会解读函数功能含义。 1. 引言 Agile Modbus 是一个轻量级的 Modbus 协议栈,可以满足用户在任何场景下的需求。 功能 支持 rtu 和 tcp 协议,使用纯 C 语言开发,不涉及任何硬…

深度学习的笔记

1. 从huggingface上仅下载pytorch模型权重和配置文件到服务器 import os import shutil from huggingface_hub import snapshot_download# 直接指定模型和下载路径 model_name openai/clip-vit-base-patch32 download_path /home/xxx/.cache/huggingface/hub/models--anas-a…