zoukankan      html  css  js  c++  java
  • SQL 查询 SELECT

    SQL 查询是通过SELECT SQL 语句来完成的。SELECT可以说是SQL中最常用的语句了。你可以把SQL语句看作是英语语句,SELECT就是SQL中的关键字之一,除了SELECT之外,还有INSERT、DELETE、UPDATE等关键字,这些关键字是SQL的保留字,这样可以很方便地帮助我们分析理解SQL语句。我们在定义数据库表名、字段名和变量名时,要尽量避免使用这些保留字。

    SELECT的作用是从一个表或多个表中检索出想要的数据行。本文极客教程主要讲解SELECT的基础查询,后面极客教程会介绍如何通过多个表的连接操作进行复杂的查询。

    SELECT查询的基础语法

    SELECT可以帮助我们从一个表或多个表中进行数据查询。我们知道一个数据表是由列(字段名)和行(数据行)组成的,我们要返回满足条件的数据行,就需要在SELECT后面加上我们想要查询的列名,可以是一列,也可以是多个列。如果你不知道所有列名都有什么,也可以检索所有列。

    创建了一个王者荣耀英雄数据表,这张表里一共有69个英雄,23个属性值(不包括英雄名name)。SQL文件见Github地址

    SQL 查询

    数据表中这24个字段(除了id以外),分别代表的含义见下图。

    SQL 查询

    查询列

    如果我们想要对数据表中的某一列进行检索,在SELECT后面加上这个列的字段名即可。比如我们想要检索数据表中都有哪些英雄。

    SELECT name FROM heros
    SQL

    运行结果(69条记录)见下图,你可以看到这样就等于单独输出了name这一列。

    SQL 查询

    我们也可以对多个列进行检索,在列名之间用逗号(,)分割即可。比如我们想要检索有哪些英雄,他们的最大生命、最大法力、最大物攻和最大物防分别是多少。

    SELECT name, hp_max, mp_max, attack_max, defense_max FROM heros
    SQL

    运行结果(69条记录):

    SQL 查询

    这个表中一共有25个字段,除了id和英雄名name以外,还存在23个属性值,如果我们记不住所有的字段名称,可以使用 SELECT * 帮我们检索出所有的列:

    SELECT * FROM heros
    SQL

    运行结果(69条记录):

    SQL 查询

    我们在做数据探索的时候,SELECT *还是很有用的,这样我们就不需要写很长的SELECT语句了。但是在生产环境时要尽量避免使用SELECT*

    起别名

    我们在使用SELECT查询的时候,还有一些技巧可以使用,比如你可以给列名起别名。我们在进行检索的时候,可以给英雄名、最大生命、最大法力、最大物攻和最大物防等取别名:

    SELECT name AS n, hp_max AS hm, mp_max AS mm, attack_max AS am, defense_max AS dm FROM heros
    SQL

    运行结果和上面多列检索的运行结果是一样的,只是将列名改成了n、hm、mm、am和dm。当然这里的列别名只是举例,一般来说起别名的作用是对原有名称进行简化,从而让SQL语句看起来更精简。同样我们也可以对表名称起别名,这个在多表连接查询的时候会用到。

    查询常数

    SELECT查询还可以对常数进行查询。对的,就是在SELECT查询结果中增加一列固定的常数列。这列的取值是我们指定的,而不是从数据表中动态取出的。你可能会问为什么我们还要对常数进行查询呢?SQL中的SELECT语法的确提供了这个功能,一般来说我们只从一个表中查询数据,通常不需要增加一个固定的常数列,但如果我们想整合不同的数据源,用常数列作为这个表的标记,就需要查询常数。

    比如说,我们想对heros数据表中的英雄名进行查询,同时增加一列字段platform,这个字段固定值为“王者荣耀”,可以这样写:

    SELECT '王者荣耀' as platform, name FROM heros
    SQL

    运行结果:(69条记录)

    SQL 查询

    在这个SQL语句中,我们虚构了一个platform字段,并且把它设置为固定值“王者荣耀”。

    需要说明的是,如果常数是个字符串,那么使用单引号(‘’)就非常重要了,比如‘王者荣耀’。单引号说明引号中的字符串是个常数,否则SQL会把王者荣耀当成列名进行查询,但实际上数据表里没有这个列名,就会引起错误。如果常数是英文字母,比如’WZRY’也需要加引号。如果常数是个数字,就可以直接写数字,不需要单引号,比如:

    SELECT 123 as platform, name FROM heros
    SQL

    运行结果:(69条记录)

    SQL 查询

    去除重复行

    关于单个表的SELECT查询,还有一个非常实用的操作,就是从结果中去掉重复的行。使用的关键字是DISTINCT。比如我们想要看下heros表中关于攻击范围的取值都有哪些:

    SELECT DISTINCT attack_range FROM heros
    SQL

    这是运行结果(2条记录),这样我们就能直观地看到攻击范围其实只有两个值,那就是近战和远程。

    SQL 查询

    如果我们带上英雄名称,会是怎样呢:

    SELECT DISTINCT attack_range, name FROM heros
    SQL

    运行结果(69条记录):

    SQL 查询

    这里有两点需要注意:

    1. DISTINCT需要放到所有列名的前面,如果写成SELECT name, DISTINCT attack_range FROM heros会报错。
    2. DISTINCT其实是对后面所有列名的组合进行去重,你能看到最后的结果是69条,因为这69个英雄名称不同,都有攻击范围(attack_range)这个属性值。如果你想要看都有哪些不同的攻击范围(attack_range),只需要写DISTINCT attack_range即可,后面不需要再加其他的列名了。

    排序检索数据

    当我们检索数据的时候,有时候需要按照某种顺序进行结果的返回,比如我们想要查询所有的英雄,按照最大生命从高到底的顺序进行排列,就需要使用ORDER BY子句。使用ORDER BY子句有以下几个点需要掌握:

    1. 排序的列名:ORDER BY后面可以有一个或多个列名,如果是多个列名进行排序,会按照后面第一个列先进行排序,当第一列的值相同的时候,再按照第二列进行排序,以此类推。
    2. 排序的顺序:ORDER BY后面可以注明排序规则,ASC代表递增排序,DESC代表递减排序。如果没有注明排序规则,默认情况下是按照ASC递增排序。我们很容易理解ORDER BY对数值类型字段的排序规则,但如果排序字段类型为文本数据,就需要参考数据库的设置方式了,这样才能判断A是在B之前,还是在B之后。比如使用MySQL在创建字段的时候设置为BINARY属性,就代表区分大小写。

    3. 非选择列排序:ORDER BY可以使用非选择列进行排序,所以即使在SELECT后面没有这个列名,你同样可以放到ORDER BY后面进行排序。

    4. ORDER BY的位置:ORDER BY通常位于SELECT语句的最后一条子句,否则会报错。

    在了解了ORDER BY的使用语法之后,我们来看下如何对heros数据表进行排序。

    假设我们想要显示英雄名称及最大生命值,按照最大生命值从高到低的方式进行排序:

    SELECT name, hp_max FROM heros ORDER BY hp_max DESC
    
    SQL

    运行结果(69条记录):

    SQL 查询

    如果想要显示英雄名称及最大生命值,按照第一排序最大法力从低到高,当最大法力值相等的时候则按照第二排序进行,即最大生命值从高到低的方式进行排序:

    SELECT name, hp_max FROM heros ORDER BY mp_max, hp_max DESC
    
    SQL

    运行结果:(69条记录)

    SQL 查询

    约束返回结果的数量

    另外在查询过程中,我们可以约束返回结果的数量,使用LIMIT关键字。比如我们想返回英雄名称及最大生命值,按照最大生命值从高到低排序,返回5条记录即可。

    SELECT name, hp_max FROM heros ORDER BY hp_max DESC LIMIT 5
    
    SQL

    运行结果(5条记录):

    SQL 查询

    有一点需要注意,约束返回结果的数量,在不同的DBMS中使用的关键字可能不同。在MySQL、PostgreSQL、MariaDB和SQLite中使用LIMIT关键字,而且需要放到SELECT语句的最后面。如果是SQL Server和Access,需要使用TOP关键字,比如:

    SELECT TOP 5 name, hp_max FROM heros ORDER BY hp_max DESC
    
    SQL

    如果是DB2,使用FETCH FIRST 5 ROWS ONLY这样的关键字:

    SELECT name, hp_max FROM heros ORDER BY hp_max DESC FETCH FIRST 5 ROWS ONLY
    SQL

    如果是Oracle,你需要基于ROWNUM来统计行数:

    SELECT name, hp_max FROM heros WHERE ROWNUM <=5 ORDER BY hp_max DESC
    
    SQL

    需要说明的是,这条语句是先取出来前5条数据行,然后再按照hp_max从高到低的顺序进行排序。但这样产生的结果和上述方法的并不一样。我会在后面讲到子查询,你可以使用SELECT name, hp_max FROM (SELECT name, hp_max FROM heros ORDER BY hp_max) WHERE ROWNUM <=5得到与上述方法一致的结果。

    约束返回结果的数量可以减少数据表的网络传输量,也可以提升查询效率。如果我们知道返回结果只有1条,就可以使用LIMIT 1,告诉SELECT语句只需要返回一条记录即可。这样的好处就是SELECT不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。

    SELECT的执行顺序

    查询是RDBMS中最频繁的操作。我们在理解SELECT语法的时候,还需要了解SELECT执行时的底层原理。只有这样,才能让我们对SQL有更深刻的认识。

    其中你需要记住SELECT查询时的两个顺序:

    1.关键字的顺序是不能颠倒的:

    SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ...

    2.SELECT语句的执行顺序(在MySQL和Oracle中,SELECT执行顺序基本相同):

    FROM > WHERE > GROUP BY > HAVING > SELECT的字段 > DISTINCT > ORDER BY > LIMIT

    一条完整的SELECT语句内部的执行顺序是这样的:

    1. FROM子句组装数据(包括通过ON进行连接);
    2. WHERE子句进行条件筛选;
    3. GROUP BY分组 ;
    4. 使用聚集函数进行计算;
    5. HAVING筛选分组;
    6. 计算所有的表达式;
    7. SELECT 的字段;
    8. ORDER BY排序;
    9. LIMIT筛选。

    比如你写了一个SQL语句,那么它的关键字顺序和执行顺序是下面这样的:

    SELECT DISTINCT player_id, player_name, count(*) as num #顺序5
    FROM player JOIN team ON player.team_id = team.team_id #顺序1
    WHERE height > 1.80 #顺序2
    GROUP BY player.team_id #顺序3
    HAVING num > 2 #顺序4
    ORDER BY num DESC #顺序6
    LIMIT 2 #顺序7
    
    SQL

    在SELECT语句执行这些步骤的时候,每个步骤都会产生一个虚拟表,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在SQL的执行过程中,对于我们来说是不可见的。

    我来详细解释一下SQL的执行原理。

    首先,你可以注意到,SELECT是先执行FROM这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:

    1. 首先先通过CROSS JOIN求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;
    2. 通过ON进行筛选,在虚拟表vt1-1的基础上进行筛选,得到虚拟表 vt1-2;

    3. 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表vt1-2的基础上增加外部行,得到虚拟表vt1-3。

    当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。

    当我们拿到了查询数据表的原始数据,也就是最终的虚拟表vt1,就可以在此基础上再进行WHERE阶段。在这个阶段中,会根据vt1表的结果进行筛选过滤,得到虚拟表vt2。

    然后进入第三步和第四步,也就是GROUP和 HAVING阶段。在这个阶段中,实际上是在虚拟表vt2的基础上进行分组和分组过滤,得到中间的虚拟表vt3和vt4。

    当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到SELECT和DISTINCT阶段。

    首先在SELECT阶段会提取想要的字段,然后在DISTINCT阶段过滤掉重复的行,分别得到中间的虚拟表vt5-1和vt5-2。

    当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是ORDER BY阶段,得到虚拟表vt6。

    最后在vt6的基础上,取出指定行的记录,也就是LIMIT阶段,得到最终的结果,对应的是虚拟表vt7。

    当然我们在写SELECT语句的时候,不一定存在所有的关键字,相应的阶段就会省略。

    同时因为SQL是一门类似英语的结构化查询语言,所以我们在写SELECT语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。

    如何提升SELECT查询效率

    当我们初学SELECT语法的时候,经常会使用SELECT *,因为使用方便。实际上这样也增加了数据库的负担。所以如果我们不需要把所有列都检索出来,还是先指定出所需的列名,因为写清列名,可以减少数据表查询的网络传输量,而且考虑到在实际的工作中,我们往往不需要全部的列名,因此你需要养成良好的习惯,写出所需的列名。

    如果我们只是练习,或者对数据表进行探索,那么是可以使用SELECT *的。它的查询效率和把所有列名都写出来再进行查询的效率相差并不大。这样可以方便你对数据表有个整体的认知。但是在生产环境下,不推荐你直接使用SELECT *进行查询。

    SQL 查询常见问题

    ORDER BY是对分的组排序还是对分组中的记录排序呢?

    ORDER BY就是对记录进行排序。如果你在ORDER BY前面用到了GROUP BY,实际上这是一种分组的聚合方式,已经把一组的数据聚合成为了一条记录,再进行排序的时候,相当于对分的组进行了排序。

    SELECT COUNT查询效率

    在MySQL中统计数据表的行数,可以使用三种方式:SELECT COUNT(*)SELECT COUNT(1)SELECT COUNT(具体字段),使用这三者之间的查询效率是怎样的?之前看到说是:SELECT COUNT(*)> SELECT COUNT(1)> SELECT COUNT(具体字段)

    解答

    在MySQL InnoDB存储引擎中,COUNT(*)COUNT(1)都是对所有结果进行COUNT。如果有WHERE子句,则是对所有符合筛选条件的数据行进行统计;如果没有WHERE子句,则是对数据表的数据行数进行统计。

    因此COUNT(*)COUNT(1)本质上并没有区别,执行的复杂度都是O(N),也就是采用全表扫描,进行循环+计数的方式进行统计。

    如果是MySQL MyISAM存储引擎,统计数据表的行数只需要O(1)的复杂度,这是因为每张MyISAM的数据表都有一个meta信息存储了row_count值,而一致性则由表级锁来保证。因为InnoDB支持事务,采用行级锁和MVCC机制,所以无法像MyISAM一样,只维护一个row_count变量,因此需要采用扫描全表,进行循环+计数的方式来完成统计。

    需要注意的是,在实际执行中,COUNT(*)COUNT(1)的执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的。

    另外在InnoDB引擎中,如果采用COUNT(*)COUNT(1)来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于COUNT(*)COUNT(1)来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。

    然而如果想要查找具体的行,那么采用主键索引的效率更高。如果有多个二级索引,会使用key_len小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。

    这里我总结一下:

    1. 一般情况下,三者执行的效率为COUNT(*)COUNT(1)COUNT(字段)。我们尽量使用COUNT(*),当然如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。
    2. 如果要统计COUNT(*),尽量在数据表上建立二级索引,系统会自动采用key_len小的二级索引进行扫描,这样当我们使用SELECT COUNT(*)的时候效率就会提升,有时候可以提升几倍甚至更高。

    SELECT LIMIT 1是否有用

    在MySQL中,LIMIT关键词是最后执行的,如果可以确定只有一条结果,那么就起不到查询优化的效果了吧,因为LIMIT是对最后的结果集过滤,如果结果集本来就只有一条,那就没有什么用了。

    解答

    如果你可以确定结果集只有一条,那么加上LIMIT 1的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度。这里指的查询优化针对的是会扫描全表的SQL语句,如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上LIMIT 1了。

    关于SQL 查询,请参与极客教程提供的SQL 教程

  • 相关阅读:
    VS2010 枚举注释任务
    osg例子中文翻译,半机翻
    怎么愉快地添加目标位置?
    变更路线节点。妈妈,我的强迫症有救啦!
    测试必备工具之抓包神器 Charles 如何抓取 https 数据包?
    测试必备工具之最强抓包神器 Charles,你会了么?
    ‘员工拒绝加班被判赔偿公司 1.8 万元’,作为测试猿你怕了么?
    全网最全测试点总结:N95 口罩应该如何测试?
    测试角度:如何看待三星大量手机系统崩溃并数据丢失事件?
    男生 vs 女生,谁更加适合做软件测试?
  • 原文地址:https://www.cnblogs.com/numpycomcn/p/11802719.html
Copyright © 2011-2022 走看看