经常有人问我那非常复杂的sql是怎么写出来的,我一直不知道该怎么回答。
       
因为虽然我写这样的sql很顺手,可是我却不知道怎么告诉别人怎么写。

很多人将这个问题归结为天赋,我却不这么看,我想这个不是天赋的问题,
       
任何人经过一定有效率的学习和练习都能完成。有的人可能学习的快点,有的
       
人可能学习的慢点,这个的确跟每个人有关,但只要经过有规律的练习,我觉得
       
还是能够很快的写出符合要求的sql的。我也一直认为,不知道怎么写是因为没有
       
找到一套行之有效的方法。

对于一个复杂的报表,乍一看,很麻烦,n多张的表的数据堆砌在一起,似乎杂乱五章
       
让初学者看了就头疼,更不用说写了。

在复杂的sql中,比较有代表性的就是报表的sql,这里我们举一个简单的例子,套用
       
凯恩教授的话,进行简单的分解:

假设有一张保险的报表,需要出如下数据(人是家庭成员数):

产品线 保险单数量 男性的数量 女性的数量 1人 2人 3人 >3人 25岁 25-35 35-45 45-55
>55

这个报表涉及到三张表,不算很复杂,分别是:
       
product
       
(
               
product_id varchar(64),
               
product_name varchar(255)
       
)

insurance
       
(
               
ins_id varchar(64),
               
product_id varchar(64),
               
cust_id varchar(64),

额度等等...
       
)

customer
       
(
               
cust_id varchar(64),
               
cust_name varchar(64),
               
cust_sex varchar(8),
               
cust_age integer,
               
family_num integer
       
)

在此我们尽可能的简化表结构,只列出我们用到的字段。
       
从报表结构上看,insurance是主表,这个任何人都能看出来。再看报表
       
产品线从product表中取,那么先将它列出来,在纸上写个字段:
       
product_id

再看保险单数量,这个从insurance表中取,其实就是count(ins_id),也写上
       
现在变成如下形式:
       
product_id ins_id

再看后面的字段,因为都是从customer表中取,所以化繁为简,变成如下形式:
       
product_id ins_id cust_id

这个就是我们的报表结构,非常简单,只有三个字段,再看product_id和cust_id
       
都是从ins_id获得,那么删掉,最后只剩ins_id,ins_id从insurance中取,
       
insurance表也就是我们的主表。这也是我们最初从报表结构上看出来的。

接着上面的,化为简单之后,我们再一步步的将报表字段逐个的填上,上面的过程
       
是将复杂化为简单,下面我们还得从简单变成复杂,毕竟我们最后要的结果是个复杂
       
的结果集。但是,从简单化为复杂,仍然要从简单入手,上面已经将一个复杂的问题
       
简化为了三个简单的问题,那么接下来我们只要分别解决三个简单的问题就可以了。
       
只要把这三个简单的问题解决了,整个报表也就完成了。

首先,根据上面的简化结果我们得到的报表结构是:
       
ins_id
       
1
       
2
       
这个就是我们简化之后的报表结构,sql描述如下:
       
select
               
ins_id
       
from insurance
       
[where ...]
       
如果有条件,完善where语句即可

第二步,补充上product信息和客户信息,也就是转换为如下结构:
       
product_id ins_id cust_id

sql语句描述如下:
       
select 
               
a.ins_id,
               
b.product_id,
               
c.cust_id
       
from
               
insurance a,
               
product b,
               
customer c
       
where 
               
a.product_id=b.product_id
               
and a.cust_id=c.cust_id

通过上面的sql,我们可以得到下面的结果集,也就是我们从上面由繁化简得到的结果集:
       
product_id ins_id cust_id
       
QD        
1     
1
       
QD        
2     
2

这里,我们使用了一个product_id代替了产品信息,使用cust_id代替客户信息,因为通过
       
product_id我们就可以拿到有关该产品的任何信息,通过cust_id我们可以拿到关于该客户的
       
任何信息,这也是简化的关键。

第三步,补充上报表需要的所有字段,这个很简单,上面已经查出来了,只需要罗列一下即可:
       
select 
               
a.ins_id,
               
b.product_id,
               
b.product_name,
               
c.cust_id,
               
c.cust_name,
               
c.cust_sex,
               
c.cust_age,
               
c.family_num
       
from
               
insurance a,
               
product b,
               
customer c
       
where 
               
a.product_id=b.product_id
               
and a.cust_id=c.cust_id
       
跟上面的sql相比,这里仅仅是把用到的字段列出来而已,并没有什么特别的地方。
       
但是,跟我们最后要的结果集相比,它还显得非常简陋,不过它已经具有了我们报表
       
需要的所有的字段了,已经具有最终报表的雏形了。
       
下面的步骤可能需要一些小小的技巧,但是也很简单,一点也不复杂。从报表结构上看
       
需要列出来的东西比上面的sql出来的字段要多很多。但实际上,所有的报表字段都是
       
从上面的sql列出来的字段经过简单处理而得来的,比如性别的数量,其实就是count(‘男’)
       
或者count('女'),年龄是count(cust_age)得到的。
       
初学者很容易陷入这样的一个陷阱:我统计男性的数量,那么怎么才能根据c.cust_sex算出
       
男性和女性的数量呢?这是初学者的思维方式还没有转换为sql语言的思维方式的原因。对于
       
上面的结果集而言,如果是人工计算的话,可能很容易看出来,但是对于数据库引擎来说,
       
我们必须告诉它一个算法,也就是告诉它怎么计算。简单的说,必须将我们的思维方式转换为
       
数据库引擎的思维方式。
       
如果要计算数量的话,我们很容易想到count函数,但是对于性别来说,怎么才能同时count出
       
男性和女性的数量呢?这只要简单的转化一下即可,把一个字段转换为两个字段,也就是把
       
性别这个字段转换为男、女两个字段(下面我们使用f代表男,m代表女):
       
select 
               
a.ins_id,
               
b.product_id,
               
b.product_name,
               
c.cust_id,
               
c.cust_name,
               
c.cust_sex,
               
c.cust_age,
               
c.family_num,
               
-- 男
               
0 as f,
               
-- 女
               
0 as m,
               
-- 其他的依次类推
               
-- 家庭成员数
               
0 as p_1,
               
0 as P_2,
               
0 as p_3,
               
0 as gt3,
               
-- 客户年龄
               
0 as lt25,
               
0 as gt25lt35,
               
0 as gt35lt45,
               
0 as gt45lt55,
               
0 as gt55
       
from
               
insurance a,
               
product b,
               
customer c
       
where 
               
a.product_id=b.product_id
               
and a.cust_id=c.cust_id

这样我们就把所有需要的字段补充完整了,到这里离我们所要的结果集已经很近了!
       
基本上已经能够看到结果集的样子了。

但是,现在的结果集还不正确,因为大部分的统计字段还都是0,我们需要对它进行转换。
       
转换完成之后,只需要分组然后count一下即可。

这里需要使用到case when语句,这个就是sql中的if else语句:
       
select 
               
a.ins_id,
               
b.product_id,
               
b.product_name,
               
c.cust_id,
               
c.cust_name,
               
c.cust_sex,
               
c.cust_age,
               
c.family_num,
               
-- 男
               
-- 这个地方根据数据库字段的不同,处理方式也不同
               
-- 如果数据库中cust_sex的数据类型本身就是0和1,那么就不需要转换
               
-- 只列出来即可
               
(case when c.cust_sex='男' then 1 else 0 end) as f,
               
-- 女
               
(case when c.cust_sex='女' then 1 else 0 end) as as m,
               
-- 其他的依次类推
               
-- 家庭成员数
               
(case when c.family_num=1 then 1 else 0 end) as p_1,
               
(case when c.family_num=2 then 1 else 0 end) as P_2,
               
(case when c.family_num=3 then 1 else 0 end) as p_3,
               
(case when c.family_num>3 then 1 else 0 end) as gt3,
               
-- 客户年龄
               
(case when c.cust_age<=25 then 1 else 0 end) as lt25,
               
(case when c.cust_age>25 and c.cust_age<=35 then 1 else 0
end) as gt25lt35,
               
(case when c.cust_age>35 and c.cust_age<=45 then 1 else 0
end) as gt35lt45,
               
(case when c.cust_age>45 and c.cust_age<=55 then 1 else 0
end) as gt45lt55,
               
(case when c.cust_age>55 then 1 else 0 end) as gt55
       
from
               
insurance a,
               
product b,
               
customer c
       
where 
               
a.product_id=b.product_id
               
and a.cust_id=c.cust_id

最后分组count一下即可:
       
select 
               
a.product_id,
               
a.product_name,
               
count(a.ins_id) as ins_num,
               
-- 性别
               
count(a.f) as f_num,
               
count(a.m) as m_num,
               
-- 成员数
               
count(a.p_1) as p_1_num,
               
count(a.p_2) as p_1_num,
               
count(a.p_3) as p_1_num,
               
count(a.gt3) as gt3_num,
               
-- 年龄
               
count(lt25) as lt25_num,
               
count(gt25lt35) as gt25lt35_num,
               
count(gt35lt45) as gt25lt35_num,
               
count(gt45lt55) as gt25lt35_num,
               
count(gt55) as gt55_num
       
from(
               
select 
                       
a.ins_id,
                       
b.product_id,
                       
b.product_name,
                       
c.cust_id,
                       
c.cust_name,
                       
c.cust_sex,
                       
c.cust_age,
                       
c.family_num,
                       
-- 男
                       
-- 这个地方根据数据库字段的不同,处理方式也不同
                       
-- 如果数据库中cust_sex的数据类型本身就是0和1,那么就不需要转换
                       
-- 只列出来即可
                       
(case when c.cust_sex='男' then 1 else 0 end) as f,
                       
-- 女
                       
(case when c.cust_sex='女' then 1 else 0 end) as as m,
                       
-- 其他的依次类推
                       
-- 家庭成员数
                       
(case when c.family_num=1 then 1 else 0 end) as p_1,
                       
(case when c.family_num=2 then 1 else 0 end) as P_2,
                       
(case when c.family_num=3 then 1 else 0 end) as p_3,
                       
(case when c.family_num>3 then 1 else 0 end) as gt3,
                       
-- 客户年龄
                       
(case when c.cust_age<=25 then 1 else 0 end) as lt25,
                       
(case when c.cust_age>25 and c.cust_age<=35 then 1 else 0
end) as gt25lt35,
                       
(case when c.cust_age>35 and c.cust_age<=45 then 1 else 0
end) as gt35lt45,
                       
(case when c.cust_age>45 and c.cust_age<=55 then 1 else 0
end) as gt45lt55,
                       
(case when c.cust_age>55 then 1 else 0 end) as gt55
               
from
                       
insurance a,
                       
product b,
                       
customer c
               
where 
                       
a.product_id=b.product_id
                       
and a.cust_id=c.cust_id
       
) a
       
group by b.product_id, b.product_name

到现在未知,我们所要的结果就完全出来了。整个过程是一个化繁为简,再由简单堆砌为复杂的过程。
       
对于初学者,培养出这样的思维方式似乎还很难,但是只要经过一两个这样的需求的练习,这中思维
       
方式就很容易形成了,到最后,当你看到一个报表结构的时候,这样的思维过程仅仅是一瞬间的事,
       
你的脑海里是n张的数据表格,经过相应关联之后,你的脑海里得到是报表结构的前一张结果集的结构,
       
然后再往前推前一张结果集的结构,直到推到主表,然后再正向推一编,最后推到完整的报表结构,
       
这个思维过程非常快,很可能再你的脑子只推一两步的时候,你就已经知道怎么写了。

谨以此文送给初学sql的朋友们。

如何写复杂的SQL的更多相关文章

  1. 如何写出高性能SQL语句

    优化SQL查询:如何写出高性能SQL语句 1.首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生欀如一条SQL语句如果 ...

  2. MySQL写出高效SQL

    mysql设计标准事务处理标准索引使用标准约束设计sql语句标准 怎么写出高效SQL清晰无误的了知业务需求满足业务需求,不做无用功知道表数据量和索引基本情况知道完成SQL需要扫描的数据量级SQL执行计 ...

  3. FreeSql (二十七)将已写好的 SQL 语句,与实体类映射进行二次查询

    有时候,我们希望将写好的 sql 语句,甚至是存储过程进行查询,虽然效率不高(有时候并不是效率至上). 巧用AsTable var sql = fsql.Select<UserX>() . ...

  4. 怎么避免写出慢SQL

    在大多数实际的系统中,慢 SQL 消耗掉的数据库资源,往往是正常 SQL 的几倍.几十倍甚至几百倍. 怎样才能在开发阶段尽量避免写出慢 SQL 呢? 估算数据量 慢 SQL 对数据库的影响,是一个量变 ...

  5. 优化SQL查询:如何写出高性能SQL语句

    1. 首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条SQL语句如果用来从一个 10万条记录的表中查1条 ...

  6. 如何手写一款SQL injection tool?

    0×01 前言 我想在FreeBuf上出没的人一般都是安全行业的,或者说是安全方面的爱好者,所以大家对sql注入应该都比较了解,反正我刚入门的时候就是学的这些:sql注入.xss之类的.sql注入从出 ...

  7. 优化 SQL 查询:如何写出高性能SQL语句

    1. 首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条SQL语句如果用来从一个 10万条记录的表中查1条 ...

  8. Sql Server 优化 SQL 查询:如何写出高性能SQL语句

    1. 首先要搞明白什么叫执行计划? 执行计划是数据库根据SQL语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条SQL语句如果用来从一个 10万条记录的表中查1条 ...

  9. 如何写出高性能SQL语句(文章摘自web开发者)

    (声明:本文内容摘自web开发者,仅供收藏学习之用,如有侵权请作者联系博主,博主将在第一时间删除) 原文地址:http://www.admin10000.com/document/484.html 1 ...

随机推荐

  1. 线性判别分析LDA原理总结

    在主成分分析(PCA)原理总结中,我们对降维算法PCA做了总结.这里我们就对另外一种经典的降维方法线性判别分析(Linear Discriminant Analysis, 以下简称LDA)做一个总结. ...

  2. 【社工】NodeJS 应用仓库钓鱼

    前言 城堡总是从内部攻破的.再强大的系统,也得通过人来控制.如果将入侵直接从人这个环节发起,那么再坚固的防线,也都成为摆设. 下面分享一个例子,利用应用仓库,渗透到开发人员的系统中. 应用仓库 应用仓 ...

  3. 07.LoT.UI 前后台通用框架分解系列之——强大的文本编辑器

    LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...

  4. Matlab数值计算示例: 牛顿插值法、LU分解法、拉格朗日插值法、牛顿插值法

    本文源于一次课题作业,部分自己写的,部分借用了网上的demo 牛顿迭代法(1) x=1:0.01:2; y=x.^3-x.^2+sin(x)-1; plot(x,y,'linewidth',2);gr ...

  5. jQuery学习之路(6)- 简单的表格应用

    ▓▓▓▓▓▓ 大致介绍 在CSS技术之前,网页的布局基本都是依靠表格制作,当有了CSS之后,表格就被很多设计师所抛弃,但是表格也有他的用武之地,比如数据列表,下面以表格中常见的几个应用来加深对jQue ...

  6. vue入门学习(基础篇)

    vue入门学习总结: vue的一个组件包括三部分:template.style.script. vue的数据在data中定义使用. 数据渲染指令:v-text.v-html.{{}}. 隐藏未编译的标 ...

  7. 使用Expression实现数据的任意字段过滤(1)

    在项目常常要和数据表格打交道. 现在BS的通常做法都是前端用一个js的Grid控件, 然后通过ajax的方式从后台加载数据, 然后将数据和Grid绑定. 数据往往不是一页可以显示完的, 所以要加分页: ...

  8. js闭包for循环总是只执行最后一个值得解决方法

    <style> li{ list-style: none;width:40px;height: 40px;text-align:center;line-height: 40px;curso ...

  9. BI分析受阻?FineBI推出SPA螺旋式分析新功能!

    过去,企业级的数据分析通常会有这么几种场景,业务部门托信息部门分析数据,结果报表一出,唇枪舌剑争论你我高低,数据不准,指标不对.信息部门欠缺业务概念,业务部门不懂技术逻辑,数据分析之路,暂时搁浅. 后 ...

  10. Android之三种网络请求解析数据(最佳案例)

    AsyncTask解析数据 AsyncTask主要用来更新UI线程,比较耗时的操作可以在AsyncTask中使用. AsyncTask是个抽象类,使用时需要继承这个类,然后调用execute()方法. ...