(4) PIVOT 和 UPIVOT 的使用

本文详细介绍了SQL Server中的PIVOT和UNPIVOT操作符的使用方法,通过具体实例展示了如何将行数据转换为列数据(PIVOT),以及如何将列数据转换为行数据(UNPIVOT)。这些技巧对于处理报表数据特别有用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近项目中用到了行转列,使用SQL SERVER 提供的PIVOT实现起来非常容易。

官方解释:详见这里

可以使用 PIVOT 和 UNPIVOT 关系运算符将表值表达式更改为另一个表。

PIVOT 通过将表达式某一列中的唯一值转换为输出中的多个列来旋转表值表达式,并在必要时对最终输出中所需的任何其余列值执行聚合。UNPIVOT 与 PIVOT 执行相反的操作,将表值表达式的列转换为列值。

其实PIVOT 就是行转列,UNPIVOT就是列转行。

PIVOT 的完整语法为:

SELECT <非透视的列>,

    [第一个透视的列] AS <列名称>,

    [第二个透视的列] AS <列名称>,

    ...

    [最后一个透视的列] AS <列名称>,

FROM

    (<生成数据的 SELECT 查询>)

    AS <源查询的别名>

PIVOT

(

    <聚合函数>(<要聚合的列>)

FOR

[<包含要成为列标题的值的列>]

    IN ( [第一个透视的列], [第二个透视的列],

    ... [最后一个透视的列])

) AS <透视表的别名>

<可选的 ORDER BY 子句>;

UNPIVOT的完整语法相对简单一些为:

SELECT <其他列>,<虚拟列别名>,<列值别名>

UNPIVOT(

<列值别名>

FOR <虚拟列别名>

IN(<第一个真实列>,<第二个真实列>....)

) AS <表别名>

我们来看一个简单PIVOT的例子,项目有如下要求:根据用户输入的查询月份,统计所有设备房间此月的告警次数,界面报表要求如下格式:

设备房间告警A次数告警B次数告警C次数
XXX1012
ZZZ105





例如:数据库中有如下表和数据:

--机房表
create table t_DevRoom
( 
  RoomId int identity(1,1),
  RoomName nvarchar(50),
  constraint  [Pk_DevRoom_RoomId] primary key clustered(RoomId),
  constraint  [Uq_DevRoom_RoomName] unique (RoomName)
)
go

--告警类型
create table t_AlarmType
(
  TypeId int,
  TypeName nvarchar(20) not null,
  constraint  [Pk_AlarmType_TypeId] primary key clustered(TypeId),
  constraint  [Uq_AlarmType_TypeName] unique (TypeName)
)
go

--告警表
create table t_Alarm 
(
  AlarmId int identity(1,1),
  RoomId int not null,
  AlarmType int not null,  
  AlarmDt datetime not null,
  constraint [Pk_Alarm_AlarmId] primary key clustered(AlarmId),
  constraint [Fk_Alarm_RoomId] foreign key (RoomId) references t_DevRoom(RoomId) on delete cascade,
  constraint [Fk_Alarm_AlarmType] foreign key (AlarmType) references t_AlarmType(TypeId) on delete cascade
)
go

insert into t_DevRoom values ('机房A')
insert into t_DevRoom values ('机房B')
insert into t_DevRoom values ('机房C')

insert into t_AlarmType values (1,'空调告警')
insert into t_AlarmType values (2,'烟雾告警')
insert into t_AlarmType values (3,'设备告警')

insert into t_Alarm values(1,1,'2013-01-01')
insert into t_Alarm values(1,1,'2013-01-02')
insert into t_Alarm values(1,2,'2013-01-02')
insert into t_Alarm values(1,3,'2013-01-03')
insert into t_Alarm values(1,3,'2013-01-04')

insert into t_Alarm values(2,2,'2013-01-01')
insert into t_Alarm values(2,2,'2013-01-02')
insert into t_Alarm values(2,3,'2013-01-02')
insert into t_Alarm values(2,3,'2013-01-03')
insert into t_Alarm values(2,3,'2013-01-04')

有了上面的临时数据,我们可以查询一下2013年1月份所有机房的告警次数:

select R.RoomId,R.RoomName,count(A.AlarmType) as nums,T.TypeName from t_DevRoom as R
left join t_Alarm as A on R.RoomId=A.RoomId
left join t_AlarmType AS T on A.AlarmType=T.TypeId
WHERE datepart(year,A.AlarmDt)=2013 AND datepart(month,A.AlarmDt)=1 or A.AlarmDt is null
group by R.RoomId,R.RoomName,T.TypeName
order by RoomId

结果如下:

我们来把这个结果集PIVOT一下,以符合我们的界面要求,我们根据语法格式进行这样修改:

select RoomId,RoomName,
       alarm_kt=isnull([空调告警],0),
       alarm_yw=isnull([烟雾告警],0),
       alarm_dv=isnull([设备告警],0)
from
(
select R.RoomId,R.RoomName,T.TypeName,count(A.AlarmType) as nums from t_DevRoom as R
left join t_Alarm as A on R.RoomId=A.RoomId
left join t_AlarmType AS T on A.AlarmType=T.TypeId
WHERE datepart(year,A.AlarmDt)=2013 AND datepart(month,A.AlarmDt)=1 or A.AlarmDt is null
group by R.RoomId,R.RoomName,T.TypeName
) as temp
pivot
(
   min(nums) for TypeName IN([空调告警],[烟雾告警],[设备告警])
) as temp2
order by RoomId

查询结果如下:

至于 UNPIVOT 与PIVOT正好相反,也来看个例子,此例子来自于网上:

create table t_score
(
  姓名 varchar(10),
  语文 int,
  数学 int,
  物理 int
)
go

insert into t_score values('张三',74,83,93)
insert into t_score values('李四',74,84,94)


select * from t_score
select 姓名,课程,分数 from t_score unpivot ( 分数 for 课程 in([语文],[数学],[物理]) ) as t go

执行结果如下:

转载于:https://www.cnblogs.com/lxblog/p/3380870.html

<think>好的,我现在需要帮助用户理解PIVOTUNPIVOT函数的用法,以及如何实现按序号依次计算金额减去交易额的功能。首先,我需要回顾用户之前的问题我的回答,确保一致性。用户之前的提问是关于按序号处理交易记录,每次减去交易额,我之前给出了Python列表循环Pandas两种方法。现在用户进一步询问PIVOTUNPIVOT使用,以及如何结合累减逻辑。首先,我需要解释PIVOTUNPIVOT在SQLPandas中的用途。PIVOT用于将行转换为列,适用于数据透视;而UNPIVOT则是其逆操作,将列转回行。需要结合用户提供的引用内容,特别是引用[2][3]中的例子,说明具体语法应用场景。然后,关于按序号累减交易额,用户可能希望了解如何在数据库使用这些函数来实现。不过,累减通常需要窗口函数或递归查询,而PIVOT可能不太直接适用。需要指出这一点,并建议使用其他方法如窗口函数,同时保留之前提到的Python实现作为替代方案。接下来,我需要考虑用户可能的实际应用场景,比如财务报表或库存管理,这些地方可能需要数据透视逐笔计算余额。要确保示例贴近实际,比如用销售数据或交易记录来演示PIVOT/UNPIVOT,并展示累减的效果。 需要注意用户提到的引用[1]中的Pandas数据处理方法,可能需要将Pandas的实现与SQL函数对比,说明各自的优缺点,比如Pandas的向量化操作更高效,适合结构化数据,而SQL适合大数据量的处理。此外,用户可能对如何处理异常值或性能优化有疑问,但当前问题集中在函数用法累减逻辑,所以应在回答中保持重点,相关问题可以引导到这些方面。最后,确保回答结构清晰,分点讲解,代码示例正确,并正确引用用户提供的参考资料。同时,生成的相关问题要自然,帮助用户深入探索可能的下一个步骤。</think>### 一、PIVOT/UNPIVOT 函数详解 PIVOT UNPIVOT 是数据转换的核心操作,用于行列转换,适用于数据库结构化数据处理工具(如Pandas)。以下分场景说明其用法: --- #### 1. PIVOT 函数(行转列) **功能**:将行数据按指定维度转换为列,常用于聚合统计[^3]。 **SQL 实现示例**(Oracle): ```sql -- 将年份作为列,统计各类型指标的最大值 SELECT FNAME, a2019, a2020, a2021, a2022 FROM V_BP_ZYJJZB PIVOT ( MAX(FVALUE) FOR F_SYS_YEAR IN ('2019' AS a2019, '2020' AS a2020, '2021' AS a2021, '2022' AS a2022) ) ``` **输出**: | FNAME | a2019 | a2020 | a2021 | a2022 | |---------|-------|-------|-------|-------| | 销售额 | 500 | 600 | 700 | 800 | **Pandas 实现**: ```python import pandas as pd df = pd.DataFrame({ '年份': ['2019', '2020', '2021', '2022'], '销售额': [500, 600, 700, 800] }) pivot_df = df.pivot(columns='年份', values='销售额') ``` --- #### 2. UNPIVOT 函数(列转行) **功能**:将宽表(多列)转换为长表(多行),便于后续分析[^2]。 **SQL 实现示例**: ```sql WITH Pvt AS ( SELECT * FROM #ShoppingCart PIVOT(SUM([TotalPrice]) FOR [Category] IN ([饼干],[果冻],[面包])) AS a ) SELECT [Name], [Category], [TotalPrice] FROM Pvt UNPIVOT([TotalPrice] FOR [Category] IN ([饼干],[果冻],[面包])) AS T ``` **输出**: | Name | Category | TotalPrice | |--------|----------|------------| | Alice | 饼干 | 50 | | Alice | 果冻 | 30 | **Pandas 实现**: ```python unpivot_df = pd.melt(pivot_df, id_vars=['FNAME'], var_name='年份', value_name='销售额') ``` --- ### 二、按序号累减交易额的实现 #### 1. SQL 实现(窗口函数) 在数据库中,使用 `SUM() OVER` 窗口函数实现累减: ```sql SELECT 序号, 交易额, SUM(交易额) OVER (ORDER BY 序号 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS 累计交易额, 初始金额 - SUM(交易额) OVER (ORDER BY 序号) AS 剩余金额 FROM transactions ``` **公式**: $$ R_n = R_0 - \sum_{i=1}^n T_i $$ 其中: - $R_0$:初始金额 - $T_i$:第$i$次交易额 --- #### 2. Pandas 实现(向量化计算) ```python import pandas as pd df = pd.DataFrame({ '序号': [1, 2, 3, 4, 5], '交易额': [200, 150, 300, 500, 100] }) initial_amount = 10000 df['剩余金额'] = initial_amount - df['交易额'].cumsum() ``` **输出**: | 序号 | 交易额 | 剩余金额 | |------|--------|----------| | 1 | 200 | 9800 | | 2 | 150 | 9650 | --- ### 三、PIVOT/UNPIVOT 与累减结合案例 **场景**:按用户统计每月交易总额,并计算逐月剩余预算 1. **原始数据**: | 用户 | 月份 | 交易额 | |-------|------|--------| | Alice | 1月 | 200 | | Alice | 2月 | 150 | 2. **PIVOT处理**(按月份展开): ```sql SELECT * FROM transactions PIVOT(SUM(交易额) FOR 月份 IN ('1月', '2月')) ``` | 用户 | 1月 | 2月 | |-------|------|------| | Alice | 200 | 150 | 3. **UNPIVOT恢复**并计算剩余金额: ```sql WITH Pvt AS (...) SELECT 用户, 月份, 交易额, 初始金额 - SUM(交易额) OVER (PARTITION BY 用户 ORDER BY 月份) FROM Pvt UNPIVOT(...) ``` --- ### 四、对比总结 | 方法 | 适用场景 | 性能 | |---------------|----------------------------|------------| | SQL窗口函数 | 数据库内直接计算 | 高并发优化 | | Pandas向量化 | 中小数据集快速处理 | 内存友好 | | PIVOT/UNPIVOT | 行列结构转换需求 | 依赖索引 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值