SQL基础培训

SQL基础培训专题

 

培训目标:了解SQL(select,update,insert)的基本写法、复杂写法,

     在有索引的情况下,如何写出高效的SQL。

 

 

 

第一章实例环境一描述

  学生成绩管理系统

课程信息表 xj_course(CrsID,CrsName, CrsNote )

学生信息表 xj_stu   (StuID, ClsID, StuName, Birthday, Address, Tel, City )

教师信息表 xj_tea   (TeaID, TeaName, address )

分数表     xj_score (StuID, ClsID, CrsID, TeaID, score )

课程所属关系信息表 xj_clscrs(ClsID, CrsID,TeaID)

 

 

 

建立环境实例

  环境的建立:

 MySQL 4.1.10a-max-log, DB2版本。

 


第二章基本SQL语法介绍

2.1       Select

  最基本的语句,MySQL的语法为:

 Select  [ All | Distinct ]select_list

       [ into outFile ‘FileName’ export_options |into dumfile ‘fileName’]

  From tabl_list

   [Wherewhere_expr ]

  [Group by Column desc|asc]

   [Having where_expr]

  [Order by ColName asc|desc ]

   [limit n,m]

  [For update |Lock in share mode]

 2.1.1,distinct 是控制返回的数据重复行的。

  selectdistinct City ,ClsID    From xj_stu

select distinct ClsID   Fromxj_stu

2,into 选项能把这个结果导出到外面的文本文件中。

  mysql>select * into outfile 'wangxl.txt' From xj_stu ;

Query OK, 27 rows affected (0.00 sec)

 

mysql> select * into dumpfile'xj_stu.txt' From xj_stu ;

ERROR 1172 (42000): Result consisted ofmore than one row

mysql> select * into dumpfile'xj_stu.txt' From xj_stu where stuid='STU001' ;

Query OK, 1 row affected (0.00 sec)

[root@fxs001 /]# find / -name"xj_stu.txt"

/usr/local/mysql-max-4.1.10a-pc-linux-gnu-i686/data/sqldb/xj_stu.txt

3,From tabl_list 列举要检索的数据表或者子查询。

  这里要讲究的东西还挺多的,

  A,可以设置别名: From xj_stu S, xj_Cls C 。别名别重复、别是关键字就行。

  B,可以把一个子查询当作一个表。select * From  (select distinct ClsID   From xj_stu ) a 。

    其实放在 From 后面的东西别理解成表,直接理解成一个结果集,一个通过各种手段

构造的子查询,这样就为写很多复杂的SQL作意识上的准备。

  C,联合查询,就在这个中间体现了,一般的有:

   tableA A inner join tableB B on A.ID = B.ID   两个都有的来连接。去掉inner也一样。

   tableA A left outer join tableB on A.ID = B.ID  A中所有的连接起来,去掉outer也一样。

  D,USE/IGNORE/FORCE INDEX 来特别提醒MySQL来进行有些索引得取舍。

4,where 子句

  是用来限定条件或者建立 表、子查询关联的语句。

5,limit 分页中常用

 LIMIT 子句可以被用于强制SELECT 语句返回指定的记录数。LIMIT接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1):

 mysql> SELECT * FROM table LIMIT 5,10;  # 检索记录行 6-15

为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:

 mysql> SELECT * FROM table LIMIT 95,-1; # 检索记录行 96-last.

如果只给定一个参数,它表示返回最大的记录行数目:

mysql> SELECT * FROM table LIMIT 5;     # 检索前 5 个记录行

换句话说,LIMIT n 等价于 LIMIT 0,n。

6,Group by

 是用来进行统计分组合计的功能,后面接要进行统计分组的字段或者表达式。

 

Count(*)

返回由一个 SELECT 语句检索出来的记录行中非 NULL 值的记录总数目:

insert into xj_cls values('9999','tempClass')

select count(S.ClsID)  From xj_stu S left    join  xj_cls C on S.ClsID =C.ClsID  #28

select count(ClsName)  From xj_stu S left    join  xj_cls C on S.ClsID =C.ClsID #27

select count(*)  From xj_stu S left    join  xj_cls C on S.ClsID =C.ClsID        #28

 

COUNT(DISTINCT expr,[expr...])

返回一个互不相同的非NULL 的值的总数目:

select count(distinct stuid ) From xj_score; 27

select count(* ) From xj_score ; 81

 

 

在 MySQL 中,通过给出一个表达式列表,可以得到不包含 NULL 的不同的表达式组合的数目。

 

AVG(expr)

返回 expr 的平均值:

select stuID,avg(score)  From xj_score   Group by StuID ; 

select stuID,avg(score)  From xj_score where stuid=’12345’  Group by StuID ; (结果条数为0)

 

 

MIN(expr)

MAX(expr)

返回 expr 的最小或最大值。MIN() 和 MAX() 可以接受一个字符串参数;在这种情况下,它们将返回最小或最大的字符串传下。查看章节 5.4.3 MySQL 如何使用索引。

select stuID,min(score),max(score)  From xj_score   Group by StuID

 

SUM(expr)

返回 expr 的总和。注意,如果返回集中没有从我任何记录行,它将返回 NULL !

select sum(score) From xj_score whereStuID='001001'

select sum(score) From xj_score whereStuID='001001'  Group by StuID

 

7,Order by 排序

   可以指定多个字段排序,可以指定别名、需要进行排序,也可以进行表达式进行排序。

  如果继续排序的结果比较多,MySQL会利用 临时文件进行排序。

例如:

select * From xj_stu whereClsID='CL001' 

 Order by   (case when StuName='zz'then '00000' else StuID end )

 就是要把 学生zz排在第一位,其他的按照学号来排列。

 

2.2       MySQL用于select 和where 子句的函数介绍

http://www.phpe.net/mysql_manual/06-3.html#Other_Functions

大家可以自己去看,如果不属性的话,最好每个都动手写一个sql来试一下,加深一下感情。

这里只列举几个常用的,

 

(Case when then  when ..then else end )

select stuID ,( case when  score >=90 then 'A' 

                       when score >= 80 andscore <90 then 'B'

                       when score >= 70 andscore <80 then 'C'

                       when score <70  then 'D' 

            end ) Grad,score

From xj_score

 

统计一下班级 CL001班的分数中,各个等级的人数分布,

select ( case when  score >=90 then 'A' 

                       when score >= 80 andscore <90 then 'B'

                       when score >= 70 andscore <80 then 'C'

                       when score <70  then 'D' 

            end ) Grad ,count(*)

From xj_score

where ClsID='CL001'

Group by 1

Grad    count(*)   

 ------- -----------

 A       3          

 B       6          

 C       3          

 D       7       

 

换一个格式显示一下,这个格式 叫做 “中国财务习惯”,很多地方用到。。

select ClsID,

sum(( case when  score >=90 then 1  else 0 end )) A ,

sum(( case when  score >= 80 and score <90  then 1 else 0 end )) B ,

sum(( case when  score >= 70 and score <80  then 1 else 0 end )) C ,

sum(( case when  score <= 70                              then 1  else 0 end )) D                   

From xj_score

Group by ClsID

ClsID    A     B     C    D   

 -------- ----  ----  ---- ----

 CL001    3     6     3    7   

 CL002    6     5     3    10  

 CL003    3     3     3    6   

 CL004    5     4    6     6   

 

其实就是传说中的 竖的格式转横的格式。

 

第三章 这个学籍管理系统中可能用到的统计需求

要做统计得首先完全清楚这个数据库的设计。请大家再次熟悉一下这个简单的数据库结构。

统计一下 各个班级的人数并列举一下来自哪里

Select ClsID,count(*) Cnt  ,Group_concat(distinct City) CitySS

From xj_stu

Group by ClsID  

ClsID    Cnt     CitySS        

 --------  ------ --------------

 CL001    7       dl            

 CL002    8       bj,dl,shanghai

 CL003    5       dl,tj         

 CL004    7       dl,shyang     

 CL999    1       dalian  

 

注意一下 这里的 Group_concat 函数是MySQL特有的,别的数据库没有,得自己写函数来完成这个列到行得转换。

 

2.3       统计参加考试的学生的均分在 85分以上的人的清单

select stuID,avg(score) avgScore Fromxj_score sc

where score >=85

Group by stuID

Order by 2 desc

 

看看 和下面的有什么区别:

select stuID,avg(score) avgScore Fromxj_score sc

Group by stuID

having avg(score) >=85

Order by 2 desc

 

哪个是正确的 ??

 

2.4       列一下各班级各科目分数的前3名。

要求结果为:班级、学号、姓名、科目名称、成绩、名次 。

 

selecta.ClsID,a.stuID,stuName,CrsName,a.score,rank From (

select ClsID,CrsID,stuID,score ,(selectcount(*) From xj_score s2 where s2.ClsID=s1.ClsID and s2.CrsID=s1.CrsID ands2.score >=s1.score) Rank

 Fromxj_score s1

) a ,xj_stu s,xj_course cc

where Rank <=3 and s.stuID=a.stuID andcc.crsID = a.crsID

Order by a.ClsID,a.CrsID,rank

 

“名次” 就是排行第几的意思,在MySQL中是弱项,需要通过嵌套子查询来实现,

在 DB2,Orace 中可以使用 分析函数 rank() OVER ( partitionby  ClsID,CrsID ORDER BY Score desc) 或者 dense_rank() OVER ( partitionby  ClsID,CrsID ORDER BY Score desc) 来实现,这个 rank() 和 dense_rank() 是非常有用的分析函数,在很多时候能解决很多棘手的问题。

 

课外思考:如何用一个SQL来 找出一个用户流水账户中断档的记录。

表:T_Cash表(CashID,CustomerID,CashAmount,CashBalace)

CID         CUSTOMER_ID     CASHFLOW_AMOUNT     CASH_BALANCE   

ASH00000035  70705813        -800                        1054228        

ASH00000076  70705813        -800                        1053428        

 ASH00000554 70705813        15200                       1068542        

 ASH90001822 70705813        314                         1053742        

 ASH90001823 70705813        -416                        1053326        

 ASH90001824 70705813        314                         1053640        

 ASH90001825 70705813        -416                        1053224        

 ASH90001826 70705813        -416                        1052808        

 ASH90001827 70705813        314                         1053122        

 

注意看 CASH_BALANCE 字段的内容是不断变化的,变化的量是当前那条记录的 CashFlowAmount的值, 如果 上图中 红字的那条记录没了,你有什么方法来检查出来是哪条前后的数据对不上了?(可以用 Rank() (只Db2/Oracle中) ,也可以用子查询来做)

 

2.5       找出没有参加考试的学生清单。

要求结果为:班级、学号、姓名、缺考科目 。

思路分析,首先得构造一个应该考试的清单,它表示全部应该考的内容,然后和已经考试的结果进行左外连接,如果没有匹配到的就是没有考的内容。

 

select Al.* From

(select s.ClsID,s.StuID,s.StuName,cc.CrsIDFrom xj_stu s,xj_clscrs cc

where s.ClsID=cc.ClsID ) Al left joinxj_score sc on Al.CrsID=sc.crsID and Al.stuID=sc.stuID and Al.ClsID =sc.ClsID

where sc.Stuid is null

ClsID    StuID     StuName     CrsID   

 -------- --------  ----------  --------

 CL001    STU001    zhangyi     Crs001  

 CL001    STU002    zhzhb       Crs001  

 

 

2.6       统计各个班级参加考试的平均分,按照平均分从高到低。

要求结果:班级号、平均分

 

select clsID,avg(score) From xj_score

Group by ClsID

order by 2 desc

clsID    avg(score)   

 -------- -------------

 CL004    76.007619    

 CL002    73.906667    

 CL003    73.885333    

 CL001    73.658421    

2.7       统计本次考试各科成绩的及格率,按照及格率从高到低排序。

其实就是分析一下本次考试的整体难度程度,

格式1:科目、及格人数、总考试人数、平均分、及格率。(3行数据)

 

select sc.CrsID,

    cc.CrsName,

    sum(case when sc.score>=60 then 1 else 0 end) GCnt,

    count(*) CntTotal,

    avg(sc.score) avgScore,

    sum(case when sc.score>=60 then 1 else 0 end)/Count(*) GCntPercent

From xj_score sc,

    xj_course cc

where        sc.CrsID =cc.CrsID

Group by   sc.CrsID

Order by    GCntPercent desc

 

 

 

 CrsID     CrsName    GCnt     CntTotal     avgScore     GCntPercent   

 --------  ---------- -------  -----------  ----------- --------------

 Crs003    English    24       27           75.31963     0.89          

 Crs001    Chinese    17       25           72.3864      0.68          

 Crs002    Maths      18       27           75.348889    0.67          

 

格式2:语文及格人数、平均分、及格率,数学及格人数、平均分、及格率,英语及格人数、平均分、及格率。

 

select

max(Case when CrsName='Chinese' then GCnt else 0 end ) ChGCnt,

max(Case when CrsName='Chinese' then avgScore else 0 end) ChAvg,

max(Case when CrsName='Chinese' then GCntPercent else 0 end )ChGPercent,

max(Case when CrsName='Maths' then GCnt else 0 end ) MaGCnt,

max(Case when CrsName='Maths' then avgScore else 0 end) MaAvg,

max(Case when CrsName='Maths' then GCntPercent else 0 end )MaGPercent,

max(Case when CrsName='English' then GCnt else 0 end ) EnGCnt,

max(Case when CrsName='English' then avgScore else 0 end) EnAvg,

max(Case when CrsName='English' then GCntPercent else 0 end )EnGPercent

 

 From (

select sc.CrsID,

    cc.CrsName,

    sum(case when sc.score>=60 then 1 else 0 end) GCnt,

    count(*) CntTotal,

    avg(sc.score) avgScore,

    sum(case when sc.score>=60 then 1 else 0 end)/Count(*) GCntPercent

From xj_score sc,

    xj_course cc

where        sc.CrsID =cc.CrsID

Group by   sc.CrsID

) a

 

ChGCnt    ChAvg     ChGPercent     MaGCnt    MaAvg      MaGPercent     EnGCnt    EnAvg     EnGPercent   

 --------- --------  -------------  --------- ---------  -------------  --------- --------  -------------

 17        72.3864   0.68           18         75.348889  0.67          24         75.31963  0.89         

 

 1record(s) selected [Fetch MetaData: 0/ms] [Fetch Data: 0/ms]

 

 

 

2.8       年级排名

 学号、姓名、语文、数学、英语、平均分、年级名次 (如果有一门没有考则单科算0分)

做法有很多,以下是其中的一种:

 

selectHA.STUID,HA.STUNAME,HA.CHSCORE,HA.MASCORE,HA.ENSCORE,HB.AVGSCORE,HB.RANK

From

(

select

 sc.stuID,s.StuName,

sum((case when cc.CrsName='Chinese' then score else 0 end )) Chscore,

sum((case when cc.CrsName='Maths' then score else 0 end )) Mascore,

sum((case when cc.CrsName='English' then score else 0 end )) Enscore

 Fromxj_score sc,xj_course cc,xj_stu s

where sc.CrsID =cc.CrsID and sc.StuID =s.StuID

Group by sc.stuID,s.StuName

 )  HA, (

select A.stuID,A.avgScore,(

  select count(*) From (select sc.stuID,sum(sc.score)/3  avgScore From xj_score sc

Group by sc.StuID ) aa where A.avgScore<=aa.avgScore) Rank

 From

(select sc.stuID,sum(sc.score)/3  avgScore From xj_score sc

Group by sc.StuID ) A  ) HB

where HA.stuID = HB.stuID

Order by HB.Rank

 

 

 

 

 

第四章 笛卡尔积的危害和利用

什么是笛卡尔积

 就是有20条记录的A集合与30条集合的B集合进行关联,结果因为“一不小心”没有指定关联关键字,那么数据库将用两两组合的形式把最终的结果集返回出来,那么返回的结果就是 20 * 30 =600 条。这个“一不小心”的极端情况就是 一个 1万条记录和另一个50万条记录“不小心”进行了笛卡尔积 那就是 50亿条记录,这个对于任何一个数据库来说都是灾难。。。。。

  所以写SQL的时候,一定要注意,在表关联的时候,一定要指定能唯一关联的关键字。。

  记住的要诀就是 1,一定要关联!2,一定要有一方是唯一的关键字。

  一方是唯一的话,那么就是 N * 1 的结果,比较正常。

 

但有的时候,为了统计的需要,也可以人为的构造这样 N * n 的结果,N是指大的结果集合,n是指特定那么几个常量。

我们来看这个例子:

 

create table vvList  (Code Varchar(20),Amount decimal(10,2))

drop table CODETYPE  ;

drop table vvList  ;

Create table CODETYPE   (Code varchar(10),Name varchar(20) )

select * From CashValue

insert into CODETYPE  values ('A','吃' ) ;

insert into CODETYPE  values ('A01','蔬菜' ) ;

insert into CODETYPE  values ('A02','水果' ) ;

insert into CODETYPE  values ('A03','点心' ) ;

insert into CODETYPE  values ('B','穿' ) ;

insert into CODETYPE  values ('B01','冬衣' ) ;

insert into CODETYPE  values ('B02','夏衣' ) ;

insert into CODETYPE  values ('B03','秋衣' ) ;

insert into CODETYPE  values ('B0301','夹克' ) ;

delete from vvList  ;

insert into vvList  values ('A0101',1) ;

insert into vvList   values ('A0102',2) ;

insert into vvList   values ('A0103',3) ;

insert into vvList   values ('A0201',4) ;

insert into vvList  values ('A0202',5) ;

insert into vvList  values ('A0203',6) ;

insert into vvList  values ('B0101',7) ;

insert into vvList   values ('B0102',8) ;

insert into vvList  values ('B0103',9) ;

insert into vvList  values ('B0201',11) ;

insert into vvList   values ('B0203',17) ;

insert into vvList   values ('B0204',788) ;

 

 

select substr(Code,1,D.DD) ,

       sum(Amount)

       From vvList ,

       (select distinct length(Code) DD From CODETYPE

        union

         select 0         ) D

group by substr(Code,1,D.DD)

substr(Code,1,D.DD)     sum(Amount)   

 ----------------------  --------------

                         861           

 A                       21            

 A01                     6              

 A0101                   1             

 A0102                   2             

 A0103                   3             

 A02                     15            

 A0201                   4             

 A0202                   5             

 A0203                   6             

 B                       840           

 B01                     24            

 B0101                   7             

 B0102                   8             

 B0103                   9             

 B02                    816           

 B0201                   11            

 B0203                   17            

 B0204                   788           

 

 19record(s) selected [Fetch MetaData: 0/ms] [Fetch Data: 0/ms]

 

 [Executed: 07-11-1下午11时49分39秒 ] [Execution:94/ms]

 

这样的结果 在 DB2中有个group byrollup 可以达到类似的功能:

selectsubstr(Code,1,1),substr(Code,1,3),Code,sum(Amount) From vvList

Group byrollup(substr(Code,1,1),substr(Code,1,3),Code)

1    2     3      4   

 ---- ----  -----  ----

 All  ALL   ALL    861 

 A    ALL   ALL    21  

 B    ALL   ALL    840 

 A    A01   ALL    6   

 A    A02   ALL    15  

 B    B01   ALL    24  

 B    B02   ALL    816 

 A     A01   A0101  1   

 A     A01   A0102  2   

 A     A01   A0103  3   

 A     A02   A0201  4   

 A     A02   A0202  5   

 A     A02   A0203  6   

 B    B01   B0101  7   

 B    B01   B0102  8   

 B    B01   B0103  9   

 B    B02   B0201  11  

 B    B02   B0203  17  

 B    B02   B0204  788 

 

 19record(s) selected [Fetch MetaData: 0/ms] [Fetch Data: 78/ms]

 

 [Executed: 07-11-1下午11时47分32秒 ][Execution: 110/ms]

也能达到类似的结果。

 

 

再来思考下面一个任务,是我们实际工作遇到的,

用户表:Customer(CustomerID) 有50000条记录。

用户配置表:T_LV(SEQ decimal(20,0),CustomerID,ProductID varchar(10))

  这个表的特征是每个用户都有一条Product_ID在这里面,现在想增加另一个Product_ID(’T002’)到这个表中,问:如何来用 insert 语句来增加。

注意,是要增加 50000条记录,而且这个seq 不是自增长的,是从原有基础上逐一累加的。

 

insert into T_LV select T.SEQ +TT.CC,C.CustomerID,'T006' From Cust C,(select Max(Seq)  SEQ From T_LV) T,

(select C1.CustomerID,count(*) CC From CustC1,Cust C2 where C1.CustomerID >=C2.CustomerID group by C1.CustomerID) TT

                              whereC.CustomerID = TT.CustomerID

 

 

 

以是表结构和数据:

id                   a_id                   b_id                 flag

 

001                 a001                   b001                     Y

002                 a001                  b002                     N

003                 a002                  b001                     Y

004                 a002                  b002                     Y

 

其中   id,a_id,b_id是主键,

现在要查a_id的个数,要求是:查flag是Y的,重复的算一个,如果相同a_id的不同记录中有N的,不计算入个数。

比如上面的记录中,a001记录中有一条记录flag是N,所以不算,a002中有两条记录,并且falg都是Y,算一条。

则结果应只有1条.

1)

SELECT        COUNT(DISTINCTA.A_ID) AID

FROM    T1

WHERE        a.FLAG= 'Y'

and        NOTEXISTS (SELECT      1

       FROM   T1 B

       WHERE       A.A_ID = B.A_ID

       AND     B.FLAG = 'N')

2)

Select     count(*)

From      (select   count(a_id) d

       from     taby

       groupby        a_id having min(flag)= 'Y') a;

3)

select      sum(Y*N)

From      (select   A_ID,

              max((casewhen Flag = 'Y' then 1 else -99 end)) Y,

              min((casewhen Flag = 'N' then 0 else 1 end)) N

       From     taby

       Groupby       A_ID) d

 

 

分页技术,

 

 

你可能感兴趣的:(sql,数据库,实例,数据,索引)