面试常考!SQL行转列和列转行

部署运行你感兴趣的模型镜像

来源:优快云    作者:潇湘隐者

原文地址:www.cnblogs.com/kerrycode/archive/2010/07/28/1786547.html

大家好,我是小z~

今天和大家分享的内容是关于SQL中行转列和列转行的一些操作

行转列,列转行是我们在开发过程中经常碰到的问题。行转列一般通过CASE WHEN 语句来实现,也可以通过 SQL SERVER 的运算符PIVOT来实现。用传统的方法,比较好理解。层次清晰,而且比较习惯。但是PIVOT 、UNPIVOT提供的语法比一系列复杂的SELECT…CASE 语句中所指定的语法更简单、更具可读性。下面我们通过几个简单的例子来介绍一下列转行、行转列问题。

我们首先先通过一个老生常谈的例子,学生成绩表(下面简化了些)来形象了解下行转列

CREATE  TABLE [StudentScores]
(
  [UserName]         NVARCHAR(20),        --学生姓名
   [Subject]          NVARCHAR(30),        --科目
   [Score]            FLOAT,               --成绩
)

INSERT INTO [StudentScores] SELECT 'Nick', '语文', 80
INSERT INTO [StudentScores] SELECT 'Nick', '数学', 90
INSERT INTO [StudentScores] SELECT 'Nick', '英语', 70
INSERT INTO [StudentScores] SELECT 'Nick', '生物', 85
INSERT INTO [StudentScores] SELECT 'Kent', '语文', 80
INSERT INTO [StudentScores] SELECT 'Kent', '数学', 90
INSERT INTO [StudentScores] SELECT 'Kent', '英语', 70
INSERT INTO [StudentScores] SELECT 'Kent', '生物', 85

(提示:可以左右滑动代码)

如果我想知道每位学生的每科成绩,而且每个学生的全部成绩排成一行,这样方便我查看、统计,导出数据

SELECT
     UserName,
     MAX(CASE Subject WHEN '语文' THEN Score ELSE 0 END) AS '语文',
     MAX(CASE Subject WHEN '数学' THEN Score ELSE 0 END) AS '数学',
     MAX(CASE Subject WHEN '英语' THEN Score ELSE 0 END) AS '英语',
     MAX(CASE Subject WHEN '生物' THEN Score ELSE 0 END) AS '生物'
FROM dbo.[StudentScores]
GROUP BY UserName

查询结果如图所示,这样我们就能很清楚的了解每位学生所有的成绩了

 

接下来我们来看看第二个小列子。有一个游戏玩家充值表(仅仅为了说明,举的一个小例子),

CREATE TABLE [Inpours]
(
  [ID]                INT IDENTITY(1,1),
  [UserName]          NVARCHAR(20),  --游戏玩家
   [CreateTime]        DATETIME,      --充值时间
   [PayType]           NVARCHAR(20),  --充值类型
   [Money]             DECIMAL,       --充值金额
   [IsSuccess]         BIT,           --是否成功 1表示成功, 0表示失败
   CONSTRAINT [PK_Inpours_ID] PRIMARY KEY(ID)
)
INSERT INTO Inpours SELECT '张三', '2010-05-01', '支付宝', 50, 1
INSERT INTO Inpours SELECT '张三', '2010-06-14', '支付宝', 50, 1
INSERT INTO Inpours SELECT '张三', '2010-06-14', '手机短信', 100, 1
INSERT INTO Inpours SELECT '李四', '2010-06-14', '手机短信', 100, 1
INSERT INTO Inpours SELECT '李四', '2010-07-14', '支付宝', 100, 1
INSERT INTO Inpours SELECT '王五', '2010-07-14', '工商银行卡', 100, 1
INSERT INTO Inpours SELECT '赵六', '2010-07-14', '建设银行卡', 100, 1

下面来了一个统计数据的需求,要求按日期、支付方式来统计充值金额信息。这也是一个典型的行转列的例子。我们可以通过下面的脚本来达到目的

SELECT
      CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
      CASE PayType WHEN '支付宝'     THEN SUM(Money) ELSE 0 END AS '支付宝',
      CASE PayType WHEN '手机短信'    THEN SUM(Money) ELSE 0 END AS '手机短信',
      CASE PayType WHEN '工商银行卡'  THEN SUM(Money) ELSE 0 END AS '工商银行卡',
      CASE PayType WHEN '建设银行卡'  THEN SUM(Money) ELSE 0 END AS '建设银行卡'
FROM Inpours
GROUP BY CreateTime, PayType

如图所示,我们这样只是得到了这样的输出结果,还需进一步处理,才能得到想要的结果

SELECT
      CreateTime,
      ISNULL(SUM([支付宝])    , 0)  AS [支付宝],
      ISNULL(SUM([手机短信])  , 0)  AS [手机短信],
      ISNULL(SUM([工商银行卡]), 0)  AS [工商银行卡],  
      ISNULL(SUM([建设银行卡]), 0)  AS [建设银行卡]
FROM
(
   SELECT
          CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,
          CASE PayType WHEN '支付宝'     THEN SUM(Money) ELSE 0 END AS '支付宝' ,
          CASE PayType WHEN '手机短信'   THEN SUM(Money) ELSE 0 END AS '手机短信',
          CASE PayType WHEN '工商银行卡' THEN SUM(Money) ELSE 0 END AS '工商银行卡',
          CASE PayType WHEN '建设银行卡' THEN SUM(Money) ELSE 0 END AS '建设银行卡'
   FROM Inpours
   GROUP BY CreateTime, PayType
) T
GROUP BY CreateTime

其实行转列,关键是要理清逻辑,而且对分组(Group by)概念比较清晰。上面两个列子基本上就是行转列的类型了。但是有个问题来了,上面是我为了说明弄的一个简单列子。实际中,可能支付方式特别多,而且逻辑也复杂很多,可能涉及汇率、手续费等等(曾经做个这样一个),如果支付方式特别多,我们的CASE WHEN 会弄出一大堆,确实比较恼火,而且新增一种支付方式,我们还得修改脚本如果把上面的脚本用动态SQL改写一下,我们就能轻松解决这个问题

DECLARE @cmdText    VARCHAR(8000);
DECLARE @tmpSql        VARCHAR(8000);
SET @cmdText = 'SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,' + CHAR(10);
SELECT @cmdText = @cmdText + ' CASE PayType WHEN ''' +
PayType + ''' THEN SUM(Money) ELSE 0 END AS ''' + PayType
      + ''',' + CHAR(10)  FROM (SELECT DISTINCT PayType FROM Inpours ) T

SET @cmdText = LEFT(@cmdText, LEN(@cmdText) -2) --注意这里,如果没有加CHAR(10) 则用LEFT(@cmdText, LEN(@cmdText) -1)

SET @cmdText = @cmdText + ' FROM Inpours    
   GROUP BY CreateTime, PayType ';

SET @tmpSql ='SELECT CreateTime,' + CHAR(10);
SELECT @tmpSql = @tmpSql + ' ISNULL(SUM(' + PayType  + '), 0) AS ''' +
PayType  + ''','  + CHAR(10)
FROM  (SELECT DISTINCT PayType FROM Inpours ) T

SET @tmpSql = LEFT(@tmpSql, LEN(@tmpSql) -2) + ' FROM (' + CHAR(10);

SET @cmdText = @tmpSql + @cmdText + ') T GROUP BY CreateTime ';
PRINT @cmdText
EXECUTE (@cmdText);

下面是通过PIVOT来进行行转列的用法,大家可以对比一下,确实要简单、更具可读性

 

SELECT CreateTime, [支付宝] , [手机短信],[工商银行卡] , [建设银行卡]
FROM
(
   SELECT CONVERT(VARCHAR(10), CreateTime, 120) AS CreateTime,PayType, Money
   FROM Inpours
) P
PIVOT (
           SUM(Money)
           FOR PayType IN
           ([支付宝], [手机短信], [工商银行卡], [建设银行卡])
     ) AS T
ORDER BY CreateTime

有时可能会出现这样的错误:

消息 325,级别 15,状态 1,第 9 行

‘PIVOT’ 附近有语法错误。您可能需要将当前数据库的兼容级别设置为更高的值,以启用此功能。有关存储过程 sp_dbcmptlevel 的信息,请参见帮助。

这个是因为:对升级到 SQL Server 2005 或更高版本的数据库使用 PIVOT 和 UNPIVOT 时,必须将数据库的兼容级别设置为 90 或更高。有关如何设置数据库兼容级别的信息,请参阅 sp_dbcmptlevel (Transact-SQL)。例如,只需在执行上面脚本前加上 EXEC sp_dbcmptlevel Test, 90; 就OK了, Test 是所在数据库的名称。

下面我们来看看列转行,主要是通过UNION ALL ,MAX来实现。假如有下面这么一个表

Create Table ProgrectDetail
(
   ProgrectName         NVARCHAR(20), --工程名称
   OverseaSupply        INT,          --海外供应商供给数量
   NativeSupply         INT,          --国内供应商供给数量
   SouthSupply          INT,          --南方供应商供给数量
   NorthSupply          INT           --北方供应商供给数量
)

INSERT INTO ProgrectDetail
SELECT 'A', 100, 200, 50, 50
UNION ALL
SELECT 'B', 200, 300, 150, 150
UNION ALL
SELECT 'C', 159, 400, 20, 320
UNION ALL
SELECT 'D', 250, 30, 15, 15

我们可以通过下面的脚本来实现,查询结果如下图所示

SELECT  ProgrectName, 'OverseaSupply' AS Supplier,
      MAX(OverseaSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'NativeSupply' AS Supplier,
       MAX(NativeSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'SouthSupply' AS Supplier,
       MAX(SouthSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName
UNION ALL
SELECT ProgrectName, 'NorthSupply' AS Supplier,
       MAX(NorthSupply) AS 'SupplyNum'
FROM ProgrectDetail
GROUP BY ProgrectName

 

用UNPIVOT 实现如下:

SELECT ProgrectName,Supplier,SupplyNum
FROM  
(
   SELECT ProgrectName, OverseaSupply, NativeSupply,
          SouthSupply, NorthSupply
    FROM ProgrectDetail
)T
UNPIVOT  
(
   SupplyNum FOR Supplier IN
   (OverseaSupply, NativeSupply, SouthSupply, NorthSupply )
) P

●Pandas进阶文章!●取数,取数,取个屁啊!
后台回复“入群”即可加入小z数据干货交流群

您可能感兴趣的与本文相关的镜像

Qwen-Image

Qwen-Image

图片生成
Qwen

Qwen-Image是阿里云通义千问团队于2025年8月发布的亿参数图像生成基础模型,其最大亮点是强大的复杂文本渲染和精确图像编辑能力,能够生成包含多行、段落级中英文文本的高保真图像

<think> 首先,用户的问题是关于SQL行转列面试题的。用户说:"我想查找SQL行转列面试题 请问SQL行转列面试题"。我需要根据系统级指令用户可见层的要求来构建回答。 回顾系统级指令: 1. 所有行内数学表达式使用$...$格式。 2. 独立公式使用$$...$$格式并单独成段。 3. LaTeX语法正确。 4. 使用中文回答。 5. 生成相关问题。 6. 回答中引用的段落末尾自然地添加引用标识,例如:[^1] 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 用户提供了几个引用: - 引用[1]、[2]、[3]、[4]、[5] 都涉及SQL行转列转行的内容。 从引用中提取关键信息: - 引用[1]:讨论行转列,使用collect_set或collect_list函数收集数据。 - 引用[2]:行转列转行示例,使用lateral view explode。 - 引用[3]:有序行转列,使用collect_list(struct(subject, score))。 - 引用[4]:行转列转行,使用union来模拟行转列。 - 引用[5]:MySQL中的行转列问题。 用户的问题是"SQL行转列面试题",所以我需要提供SQL行转列的常见面试解决方案。 结构化我的回答: 1. **介绍SQL行转列的概念**:简要解释什么是行转列(pivot)转行(unpivot)。 2. **提供面试题示例**:给出几个典型的SQL行转列面试题。 3. **给出解决方案**:针对每个面试题提供解决方案,使用SQL代码。 4. **确保真实可靠**:基于引用内容,确保SQL语法正确,并引用相应部分。 5. **生成相关问题**:在回答末尾添加相关问题,如系统指令要求。 针对行转列,常见的面试题可能包括: - 将学生的成绩从多行(每个学科一行)转换为一行(每个学科一)。 - 使用不同的SQL方言(如Hive、MySQL)实现。 从引用中: - 引用[1][3]使用collect_list进行行转列,适用于Hive或Spark SQL。 - 引用[4]使用union模拟行转列,适用于MySQL等不支持pivot的数据库。 - 引用[2]使用explode进行转行,但用户关注行转列。 - 引用[5]提到MySQL行转列。 所以,我应该覆盖不同数据库的实现。 步骤: 1. **解释行转列**:在SQL中,行转列(pivot)是将行数据转换为数据的过程。例如,将每个学生的多个学科成绩从多行转为一行。 2. **面试题1:基础行转列** - 题目:有一个学生成绩表,包括student, subject, score。要求将每个学生的所有学科成绩转为一。 - 解决方案: - 在Hive中使用collect_list:如引用[1][3]。 - 在MySQL中使用CASE WHEN或GROUP_CONCAT:如引用[4][5]。 3. **面试题2:有序行转列** - 题目:确保学科在转换后有序,如引用[3]提到的。 - 解决方案:使用struct或order by。 4. **面试题3:使用pivot函数** - 对于支持pivot的数据库SQL Server或Oracle。 5. **代码示例**:提供SQL代码块。 在回答中引用: - 当提到collect_list时,添加[^1]或[^3]。 - 当提到union方法时,添加[^4]。 生成相关问题: - 如系统指令要求,在末尾添加相关问题,例如: 1. 如何在MySQL中实现行转列? 2. 行转列的性能考虑是什么? 3. 转行如何实现? 等等。 确保中文回答。 最终回答结构: - 开头:介绍行转列。 - 主体:分面试解决方案。 - 结尾:相关问题。 </think> 以下是对SQL行转列面试题的详细解答。我将基于您提供的引用内容(如[^1][^3][^4][^5]),结合常见面试场景,逐步解释概念、提供典型面试题及解决方案。SQL行转列(Pivot)是将多行数据转换为一行多的操作,常用于报表生成数据聚合,是SQL面试高频考点。解答均基于真实数据库实现(如Hive、MySQL),确保语法正确可靠。 ### 1. SQL行转列基础概念 行转列的核心是将重复的"行"数据(如学生各学科成绩)聚合为""。例如,原始表结构: | student | subject | score | |---------|---------|-------| | Alice | Math | 90 | | Alice | English | 85 | | Bob | Math | 75 | 转换为: | student | Math | English | |---------|------|---------| | Alice | 90 | 85 | | Bob | 75 | NULL | 关键点: - **collect_list/collect_set**:在Hive/Spark SQL中收集多行为数组(如引用[^1][^3])。 - **CASE WHEN/GROUP_CONCAT**:在MySQL中用条件聚合模拟(如引用[^4][^5])。 - **pivot函数**:在SQL Server/Oracle等数据库中直接支持(但较少在面试中要求)。 ### 2. 典型面试题及解决方案 以下是常见行转列面试题,附代码示例。假设表名为`scores`,包括`student`(学生)、`subject`(学科)、`score`(分数)。 #### **面试题1: 基础行转列(固定学科)** **题目**: 将学生成绩表按学科转为,假设学科固定为Math、English、Science。 **解决方案**(使用MySQL): - 用`CASE WHEN`条件聚合,确保每行唯一学生。 - 引用[^4]中类似方法,通过分组条件筛选实现。 ```sql SELECT student, MAX(CASE WHEN subject = 'Math' THEN score ELSE NULL END) AS Math, MAX(CASE WHEN subject = 'English' THEN score ELSE NULL END) AS English, MAX(CASE WHEN subject = 'Science' THEN score ELSE NULL END) AS Science FROM scores GROUP BY student; ``` **输出示例**: | student | Math | English | Science | |---------|------|---------|---------| | Alice | 90 | 85 | NULL | **解释**: `MAX`函数确保聚合唯一值;如果学科不固定,需动态处理(见面试题2)。 #### **面试题2: 动态学科行转列(学科不固定)** **题目**: 学科未知时,动态生成(如Hive环境)。 **解决方案**(使用Hive collect_list): - 用`collect_list`收集学科分数数组,再结合`map`或`lateral view`展开(如引用[^1][^3])。 - 注意:需额外处理数组顺序(引用[^3]强调有序需显式排序)。 ```sql -- 步骤1: 收集为数组(有序需排序) SELECT student, collect_list(subject) AS subjects, collect_list(score) AS scores FROM ( SELECT * FROM scores ORDER BY subject -- 确保有序 ) tmp GROUP BY student; -- 步骤2: 使用lateral view展开(引用[^2]方法) SELECT student, subject_col, score_col FROM ( SELECT student, map(subjects, scores) AS score_map FROM above_query ) tmp LATERAL VIEW explode(score_map) t AS subject_col, score_col; ``` **输出示例**: | student | subject_col | score_col | |---------|-------------|-----------| | Alice | Math | 90 | | Alice | English | 85 | **解释**: 若需一一值,需在应用层处理;面试常考动态性(如引用[^3])。 #### **面试题3: 使用聚合函数实现行转列(MySQL优化版)** **题目**: 在MySQL中,用`GROUP_CONCAT`将多行合并为逗号分隔值(CSV)。 **解决方案**(如引用[^4][^5]): - 结合`GROUP_CONCAT`字符串函数。 ```sql SELECT student, GROUP_CONCAT(CASE WHEN subject = 'Math' THEN score END) AS Math, GROUP_CONCAT(CASE WHEN subject = 'English' THEN score END) AS English FROM scores GROUP BY student; ``` **输出示例**: | student | Math | English | |---------|------|---------| | Alice | 90 | 85 | **解释**: `GROUP_CONCAT`默认逗号分隔,适用于简单场景;复杂时需用`SUBSTRING_INDEX`解析。 #### **面试题4: 行转列结合聚合计算(如求平均分)** **题目**: 转后计算每个学生的平均分。 **解决方案**(扩展面试题1): ```sql SELECT student, AVG(CASE WHEN subject = 'Math' THEN score ELSE NULL END) AS Avg_Math, AVG(CASE WHEN subject = 'English' THEN score ELSE NULL END) AS Avg_English FROM scores GROUP BY student; ``` **输出示例**: | student | Avg_Math | Avg_English | |---------|----------|-------------| | Alice | 90.0 | 85.0 | **解释**: 将`MAX`替换为`AVG`/`SUM`等函数,展示聚合能力(常见于面试[^4])。 ### 3. 面试注意事项 - **性能**: 行转列可能涉及全表扫描,大数据时需索引优化(如引用[^5]讨论)。 - **跨数据库**: Hive用`collect_list`,MySQL用`CASE`,SQL Server用`PIVOT`函数。 - **常见陷阱**: - 学科未排序导致结果无序(引用[^3]强调)。 - NULL值处理(用`COALESCE`替换)。 - 动态需应用层支持(如Python/Pandas)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值