问题描述

假设有张学生成绩表(CJ)如下
Name Subject Result
张三 语文 80
张三 数学 90
张三 物理 85
李四 语文 85
李四 数学 92
李四 物理 82

现在 想写 sql 语句     查询后结果 为    
姓名 语文 数学 物理
张三 80 90 85
李四 85 92 82       该怎么实现 ?

研究意义

这是个并不复杂的问题,但却是数据库中行转列的一个典型例子,只要把这个抽象出来的具有普遍意义的问题研究透彻,其他类似的复杂问题迎刃而解。

问题分析

首先介绍下行转列的概念,也许书上并没有这个概念,行转列说的是这样一类问题:有时候为了数据库表的设计满足用户的动态要求(比如添加字段),我们采用定义字段名表,然后定义一个字段值的表,这样就达到了用静态来表达动态,换句话说就是把数据库表中本来应该是横向的延伸转化为纵向的延伸,再换句话说就是把数据库表中本来应该是字段的增加转化为记录条数的增加。然而,在这样设计下,固然灵活,确带来了统计分析的麻烦,因为统计分析时,应该是以直观的形式进行表现。换言之,统计分析时,我们又应该显示为字段更多的那种。如果同时做到了数据存储时列的增加转化为行的增加,数据提取时又可得到列增加了的数据,数据库表的这种设计就对用户透明了。

本文前面提出的这个问题就是一个典型的在数据提取时要把以行增加形式的数据转化为以列增加形式的数据。为什么这样说呢?我们注意subject字段,subject里的内容在数据库存储时是以不同数据行的形式,换言之,是以行增加的形式,而输出时,这里面的内容我们要变成字段名了。

衡量这个问题解决好坏我们有几个标准:1.当数据正好就是上面这个样子时,解决办法能否得到正确的解;2.如果增加科目了科目的种类,解决方法是否仍然能行得通;3.如果有些人的某们课程的成绩还没有下来,换言之,数据库中不是每个人每门课的成绩都可以找到,数据库缺少某个人某门课的成绩的记录。在这种情况下程序还能否得到合理的结果。

试验环境

本试验使用MS SQL Server 2005环境测试。

试验过程

1.建立数据表,录入数据

CREATE TABLE [dbo].[CJ](
[name] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
[subject] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
[result] [int] NULL,
CONSTRAINT [PK_CJ] PRIMARY KEY CLUSTERED 
(
[name] ASC,
[subject] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

通过可视化界面或者用insert语句录入数据

2.第一个最直接,最简单的做法

select distinct c.[name] as 姓名,
(select result from CJ where [name] = c.[name] and subject = '语文' )as 语文,
(select result from CJ where [name] = c.[name] and subject = '数学' )as 数学,
(select result from CJ where [name] = c.[name] and subject = '物理' )as 物理
from CJ c

主要思想就是把任务分成两步,第一步:把第一列生成出来。第二步:根据第一列每行的姓名取值,查询该同学的各科成绩join到第一步生成的只有一列表。distinct不能省略。

该方法能够完成该任务,但只能满足前文所述的评价标准1和标准3。当科目增多或者实际科目没有这么多时统计的结果就不那么完美了。换言之,这种方法是静态的,将科目在sql语句里写死了。另外中间的几个sql语句查询效率似乎并不那么高,还需要扫描整个表,实际上应该只需要在一个学生对应的几条记录里找就可以了。

3.较好的办法

先不管标准2,想想能不能解决那个扫描的效率问题。于是得到了下面的办法。

select   [name] as 姓名,
sum(case when subject='语文' then result end) as 语文,
sum(case when subject='数学' then result end) as 数学,
sum(case when subject='物理' then result end) as 物理
from CJ group by [name]

该办法大致思想类似前一种。最大的改进是用了group by,由于用了group by后字段名除了group by的那个其他不能直接用,加了个集函数,实际上这个Sum只会加一项,因为这个表的主键是name + subject。用了group by就会解决扫描的效率问题,因为sum是计算的每个分组之类的。本方法的技巧之处在于case when的使用。

这个办法还是不能满足标准2。

4.较完美的办法

现在就是怎么解决subject“由死到活”的问题。想到了一种办法如下:

declare @s nvarchar(1000)
select @s = 'select [name] as 姓名'
select @s = @s + ',sum(case when subject=''' + cast(subject as varchar) + ''' then result end) as ' + subject from CJ group by subject
select @s = @s + ' from CJ group by [name]'
exec(@s)

其实思想是基于前面那种办法的,关键的地方就是通过动态生成sql语句,然后执行之。

在@s的第一次累加中的代码中一句from CJ group by subject很是有技巧性,可见简单的select * from table t where .. 也是这么变化无穷,不得不佩服sql或者说关系型数据库的智慧。

本人收获

a.认真的分析一个简单的问题的来龙去脉是很有意义的事情,浮躁的学风会让你花费大量的时间结果一无所获。

b.解决一个问题要有清晰的思路,在一时不知道完美的答案时,可试图一步一步优化,向完美的方向靠近。

c.要善于分析问题的症结所在,即抓住问题的本质。

写到最后

这个问题暂时就说到这里,之所以把文章写出来是基于两个目的,首先,作为学习心得,不敢独享,希望更多的人能从中得到启发。其次,简单的问题也包含很多高深的知识,希望更多的高手能加入探讨,分析本文的不当之处,并给出更好的办法,或者提供更多的类似的例子,本文希望起到抛砖引玉的作用。

数据库行转列的sql语句的更多相关文章

  1. 在论坛中出现的比较难的sql问题:19(row_number函数 行转列、sql语句记流水)

    原文:在论坛中出现的比较难的sql问题:19(row_number函数 行转列.sql语句记流水) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记 ...

  2. 行转列:SQL SERVER PIVOT与用法解释

    在数据库操作中,有些时候我们遇到需要实现“行转列”的需求,例如一下的表为某店铺的一周收入情况表: WEEK_INCOME(WEEK VARCHAR(10),INCOME DECIMAL) 我们先插入一 ...

  3. 数据库行转列、列转行,pivot透视多列

    这就是典型的行转列问题. 首先说下最简单的思路  用union all select year,sum(m1) m1,sum(m2) m2,sum(m3) m3,sum(m4) m4 from ( s ...

  4. Mysql下在某一列后即表的某一位置添加新列的sql语句

    Mysql简介 MySQL是一个开放源码的小型关联式数据库管理系统,开发者为瑞典MySQL AB公司.MySQL被广泛地应用在Internet上的中小型网站中.由于其体积小.速度快.总体拥有成本低,尤 ...

  5. 在论坛中出现的比较难的sql问题:1(字符串分拆+行转列问题 SQL遍历截取字符串)

    原文:在论坛中出现的比较难的sql问题:1(字符串分拆+行转列问题 SQL遍历截取字符串) 最近,在论坛中,遇到了不少比较难的sql问题,虽然自己都能解决,但发现过几天后,就记不起来了,也忘记解决的方 ...

  6. (转载)异构数据库之间完全可以用SQL语句导数据

    <来源网址:http://www.delphifans.com/infoview/Article_398.html>异构数据库之间完全可以用SQL语句导数据 告诉你一个最快的方法,用SQL ...

  7. SQL面试题:有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列

    .请教一个面试中遇到的SQL语句的查询问题 表中有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列,当B列大于C列时选择B列否则选择C列. ------------------- ...

  8. Oracle数据库查找持有锁的SQL语句,而不是请求锁的SQL语句(原创)

    Oracle数据库查找持有锁的SQL语句,而不是请求锁的SQL语句 查找活动的事务以及活动事务关联的会话信息 select s.sid 会话ID, s.serial# 会话序列号, s.usernam ...

  9. SQL面试题: 数据库中有A B C三列,用SQL语句实现:当A列大于B列时选择A列否则选择B列 ,当B列大于C列时选择B列否则选择C列 ,

    1.用一条sql语句 select (case when a>b then a else b end ),(case when b>c then b esle c end)  from 表 ...

随机推荐

  1. Canal v1.1.4版本避坑指南

    前提 在忍耐了很久之后,忍不住爆发了,在掘金发了条沸点(下班时发的): 这是一个令人悲伤的故事,这条情感爆发的沸点好像被屏蔽了,另外小水渠(Canal意为水道.管道)上线一段时间,不出坑的时候风平浪静 ...

  2. java 增强for循环与泛型

    一 增强for循环 增强for循环是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的.它的内部 原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作 ...

  3. C#LeetCode刷题之#590-N叉树的后序遍历(N-ary Tree Postorder Traversal)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4092 访问. 给定一个 N 叉树,返回其节点值的后序遍历. 例如 ...

  4. C#LeetCode刷题之#455-分发饼干(Assign Cookies)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4034 访问.. 假设你是一位很棒的家长,想要给你的孩子们一些小饼 ...

  5. day1 计算机的组成

    1.计算机,是用于高速计算的电子机器,具有数值计算, 逻辑判断,储存记忆的功能. 2.计算机由硬件系统和软件系统组成构成. 3.计算机中所有的运行程序都是在内存中进行的,暂时存放在CPU中的运算数据 ...

  6. Pycharm 2020.01亲测激活到2089年

    Pycharm 2020.01亲测激活到2089年,注意是2020.01版本. 下载jetbrains-agent.jar 链接: https://pan.baidu.com/s/1w1i15pT38 ...

  7. C++判断是回文串还是镜像串

    #include <iostream> #include <string> #include <cstdio> #include <cctype> #p ...

  8. Vue管理系统前端系列二相关工具引入及封装

    目录 sass-loader/vuex 等的引入说明 引入 element 引入 axios 1.基本使用 2.封装使用 2.1 开发环境配置请求地址 2.2 配置代理 2.3 添加接口相关文件 sa ...

  9. JavaScript学习系列博客_7_JavaScript中的逻辑运算符、三元运算符

    逻辑运算符 ! 非 - 非运算可以对一个布尔值进行取反,true变false false边true - 当对非布尔值使用!时,会先将其转换为布尔值然后再取反 - 我们可以利用 !! 来将其他的数据类型 ...

  10. linux 查找删除

    find -name "*.php" -exec rm -f '{}' \;