sql语句基础

一、SQL是声明式的

开头就直接声明你要查询的数据,也就是你想显示的结果java

SELECT first_name, last_name

FROM employees

WHERE salary > 100000

二、SQL语法不是“有序的”

形成混淆的一个常见缘由是SQL的语法元素不是按照他们的执行顺序排列的,常见语句顺序以下:程序员

  • SELECT [ DISTINCT ]:指定要显示的属性列
  • FROM:说明要查询的数据来自那个/些表
  • WHERE:指定查询条件
  • GROUP BY:对查询结果进行分组
  • HAVING:筛选出知足指定条件的组
  • UNION:用于合并两个或多个 SELECT 语句的结果集
  • ORDER BY: 对查询结果表按指定列值得升序或降序排序

(为简单起见,这里并无列出全部的SQL语句)这个语句顺序是不一样于其逻辑顺序:(也可能不一样于其执行顺序,这取决于优化器的选择)正则表达式

  • FROM
  • WHERE
  • GROUP BY
  • HAVING
  • SELECT
  • DISTINCT
  • UNION
  • ORDER BY

这里有三点须要注意的:sql

(1)FROM语句必定是首先执行的,而不是SELECT语句,由于系统要先将磁盘中的数据加载到缓存当中,以便执行接下来操做。数据库

(2)SELECT语句在大多数语句以后执行,尤为是FROM与GROUP语句。当你认为你能够在WHERE语句中引用SELECT语句声明的元素时,理解这一点就显得很重要了。好比下面的例子是不容许的:缓存

SELECT A.x + A.y AS zFROM AWHERE z = 10 -- 执行WHERE语句时,z根本不存在,见上面的逻辑顺序

若是你想在这里再次使用z,有两种方法,一是用A.x + A.y代替,以下安全

SELECT A.x + A.y AS zFROM AWHERE (A.x + A.y) = 10

或者你也能够采用派生表,通用数据表达式,或者视图,以免代码的重复,参见后续的例子。ide

(3) UNION语句无论在语法上仍是逻辑上,都在ORDER BY以前,许多人认为SELECT的结果能够先排序后合并,但根据SQL标准和许多SQL方言(注:不一样数据库的SQL语句略有差异,姑且称之为方言),这是不正确的,尽管一些方言容许对子查询的结果或者派生表进行排序,但这种结果在执行完UNION语句以后是不必定能保持的。函数

注意,不一样数据库执行语句的方式是不一样的,好比上面第二条就不适用于MySQL, PostgreSQL, 和SQLite。性能

记住SQL语句的语法顺序和逻辑顺序以免一些常见的错误,若是你理解他们的区别,你就能清楚地知道为何有些程序有些倒是错的。

三、SQL是关于表的引用的语言

因为语法顺序和逻辑顺序之间的差别,大多数初学者可能被骗,觉得列值是SQL中最重要的元素,事实上他们不是,最重要的是表的引用。

SQL标准中是这样定义FROM语句的:

<from clause> ::=     FROM <table reference>         [ { <comma> <table reference> }... ]

可见FROM语句的输出结果是一张组合表。下面咱们慢慢分析这一点:

FROM a, b

上面的语句根据表a、b的维度生成了一张组合表,例如若是a有3列,b有5列,那么输出表应当有3+5=8列,这张表中的数据是a与b的笛卡儿积(设A,B为集合(SQL中称一组具备相同数据类型的值的集合为“域”,表中的每一列对应一个域),用A中任一元素为第一元素,B中任一元素为第二元素构成有序对,全部这样的有序对组成的集合叫作A与B的“笛卡尔积”)。

这个结果通过WHERE语句过滤后进入GROUP BY语句,而后被转化为一个新的输出,稍后咱们会讲到这一点。

若是咱们从关系代数或者集合论的角度来看这个问题,一个SQL表就是一个关系或元组的集合,每一个SQL语句都会改变一个或多个关系并产生新的关系。

要多从表的引用角度来思考问题,这样才能理解各个SQL语句都是如何处理表中数据的。

四、表的引用功能十分强大

用一个简单的例子证实这一点: JOIN关键字(事实上它并非SELECT语句的一部分,而是属于一种特殊的表的引用),在SQL标准中,JOIN定义(简化的)以下:

<table reference> ::=    <table name>  | <derived table>  | <joined table>

再拿以前的例子来看:

FROM a, b

a多是这样一个联合表:

a1 JOIN a2 ON a1.id = a2.id

将其带入到前面的表达式,咱们获得:

FROM a1 JOIN a2 ON a1.id = a2.id, b

尽管咱们不鼓励将一张联合表经过逗号和另外一张表链接在一块儿,但确实能够作到这一点,结果是,最后产生的联合表将会有a1+a2+b个维度。

派生表的功能甚至比表链接更增强大,咱们后续会提到它。

多从(必定要多从)表的引用角度来考虑问题,这不只能够帮助你理解SQL语句是如何处理数据的,也将有助于你了解如何构造复杂的表引用,而且·,了解JOIN是构建联合表的关键字,而不是SELECT语句的一部分,一些数据库容许在INSERT, UPDATE, DELETE中使用JOIN。

五、SQL的链接操做中应多使用JOIN而不是逗号分隔表

以前,咱们分析了下面的语句:

FROM a, b

高级SQL程序员可能会告诉你,最好不要使用逗号分隔表,要充分利用JOIN这个关键字,这不只会提升SQL语句的可读性,并且会减小错误的发生。

好比下面的例子:

FROM a, b, c, d, e, f, g, hWHERE a.a1 = b.bxAND a.a2 = c.c1AND d.d1 = b.bc-- etc...

可见,使用join:

  • 更加安全:你能够将join放置在靠近要链接的表的地方,以此来避免错误;
  • 更具表达力:你能够区分外链接、内链接。

必定要多使用JOIN。在FROM语句中尽可能不使用逗号分隔表。

六、SQL中不一样的链接操做

链接操做主要分为五种:

  • EQUI JOIN(等值链接)
  • SEMI JOIN(半链接)
  • ANTI JOIN(ANTI-SEMI JOIN)
  • CROSS JOIN(交叉链接)
  • DIVISION(除法链接)

这些术语是关系代数中经常使用的术语。SQL对以上概念使用的术语略有不一样,让咱们详细探讨一下:

EQUI JOIN

最多见的链接操做,它又分为:

  • INNER JOIN (JOIN)
  • OUTER JOIN (进一步分为LEFT, RIGHT, FULL JOIN)

(注:

LEFT JOIN: 即便右表中没有匹配,也从左表返回全部的行

RIGHT JOIN: 即便左表中没有匹配,也从右表返回全部的行

FULL JOIN: 只要其中一个表中存在匹配,就返回行)

它们的区别能够用下面这个例子很好的说明:

— 引用了“做者”表以及“著做”表–

每一个做者对应一个著做–

不包含没有著做的做者

author JOIN book ON author.id = book.author_id

-- 引用了“做者”表以及“著做”表

-- 每一个做者对应一个著做-- ... 

存在无著做的做者,记录为空--

 “空“意味着著做所在列为空

author LEFT OUTER JOIN book ON author.id = book.author_id

SEMI JOIN

这种关系的概念在SQL中能够用的两种方式表达:IN或者EXISTS。“Semi” 在拉丁语中意味着“一半”。这种类型的链接只引用表中的“一半”。这是什么意思?(注:一般出如今使用了exists或in的sql中,所谓semi-join即在两表关联时,当第二个表中存在一个或多个匹配记录时,返回第一个表的记录;与普通join的区别在于semi-join时,第一个表里的记录最多只返回一次)再考虑一下上面做者和书的链接。让咱们想象一下,若是咱们不想要做者/书籍的组合,而只想要有著书的做者的信息。那么咱们能够这样写:

-- 用 IN

FROM author

WHERE author.id IN (SELECT book.author_id FROM book)

-- 用 EXISTS

FROM author

WHERE EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)

尽管没有规定何时用IN,何时用EXISTS,下面的事实仍是要知道的:

  • IN比EXISTS更具可读性
  • EXISTS比IN更具表现力(即更容易表达很是复杂的半链接)
  • 二者性能上没有显著差别(可是,也许在一些数据库中有巨大性能差别,这一点取决于具体数据库,上面的表述是通常状况)

由于INNER JOIN也能够生成有著做的做者的信息,许多初学者可能认为,他们可使用DISTINCT删除重复内容,他们认为他们能够表达SEMI JOIN:

-- Find only those authors who also have books

SELECT DISTINCT first_name, last_name

FROM author

JOIN book ON author.id = book.author_id

这是很是糟糕的作法,缘由有两点:

  • 性能低下,由于数据库须要将大量的数据加载到内存中,来删除重复的数据。
  • 这不是彻底正确的,即便在这个简单的例子它会产生正确的结果,可是当你引用更多的表格,那时想从结果中去重就变得十分困难了。

更多有关DISTINCT的内容能够看这篇博文:

http://blog.jooq.org/2013/07/30/10-common-mistakes-java-developers-make-when-writing-sql/

ANTI JOIN

这个关系的概念与SEMI JOIN刚好相反,你能够经过简单地在IN或者EXISTS前添加NOT来实现它(注:而anti-join则与semi-join相反,即当在第二张表没有发现匹配记录时,才会返回第一张表里的记录;当使用not exists/not in的时候会用到,二者在处理null值的时候会有所区别,见下文所附连接),例如,咱们来查询那些没有对应著做的做者信息:

-- Using IN

FROM author

WHERE author.id NOT IN (SELECT book.author_id FROM book)

-- Using EXISTSF

ROM author

WHERE NOT EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)

在性能、可读性、表现力方面,上面的规则一样适用。然而,在使用NOT IN时遇到NULLs就有点麻烦了,这个有点超出本教程的范围了。参看:

http://blog.jooq.org/2012/01/27/sql-incompatibilities-not-in-and-null-values/

CROSS JOIN

这个结果是产生两个表的笛卡儿积,咱们以前说过,也能够用逗号分隔两个表来实现。在极少数状况下,若是确实须要的话,你也能够这样明白地来写一个CROSS JOIN:

-- Combine every author with every book

author CROSS JOIN book

DIVISION

DIVISION是个奇葩,你姑且这样理解吧,若是JOIN是乘法,DIVISION就是乘法的逆运算,DIVISION很难表述,由于这是初学者的教程,就不说它了,若是你够勇敢,看这里:

http://blog.jooq.org/2012/03/30/advanced-sql-relational-division-in-jooq/

看这里:

http://en.wikipedia.org/wiki/Relational_algebra#Division

和这里:

https://www.simple-talk.com/sql/t-sql-programming/divided-we-stand-the-sql-of-relational-division/

SQL是关于表引用的,JOIN是至关复杂的表引用,SQL表述与关系表述之间是有区别的,不是全部的关系链接操做也正规SQL的链接操做。经过一点点的实践和对关系理论知识的进一步了解,你能够选择使用正确的JOIN类型,并能把它转化成正确的SQL语句。

七、SQL的派生表(能够看做SQL中的表变量)

再此以前,咱们已经了解到,SQL是声明性语言,这种语言中变量是没有地位的,但你能够写一些相似于变量的奇葩东西,这中奇葩东西就被称为派生表,派生表其实不过是一个包含在括号内的子查询。

-- 一个派生表

FROM (SELECT * FROM author)

值得注意的是,一些SQL方言须要派生表有一个相关名(也称为别名)。

-- 带别名的派生表

FROM (SELECT * FROM author) a

派生表能够有效地帮助你规避由SQL子句的逻辑顺序产生的问题。例如,若是你想在SELECT和WHERE语句中重复使用一个表达式,能够像这样写(Oracle数据库语言):

-- Get authors' first and last names, and their age in days

SELECT first_name, last_name, age

FROM (  

     SELECT first_name, last_name, current_date - date_of_birth age  

     FROM author

)

-- If the age is greater than 10000 days

WHERE age > 10000

注意某些数据库和SQL:1999标准已经将派生表发展到一个新的水平,引入了通用表表达式,容许你在一个SELECT语句中屡次使用一个派生表。那么上面的查询就至关于:

WITH a AS (  

   SELECT first_name, last_name, current_date - date_of_birth age  

   FROM author

)

SELECT *FROM a

WHERE age > 10000

固然你也能够为表“a”建立一个独立的视图以便在更普遍的范围内重复使用子查询的结果。更多关于视图的讲解请看这里:

http://en.wikipedia.org/wiki/View_%28SQL%29

再次强调,SQL是关于表引用的,要充分利用这一点,不要惧怕写派生表或者其余更复杂的表引用语句。

八、GROUP BY语句能够用来改变表引用的结果

咱们再来看一下前面的例子:

FROM a, b

如今让咱们对上面的联合表(FROM语句的操做结果)使用GROUP BY操做:

GROUP BY A.x, A.y, B.z

上述操做将会生成一个只有三列的新表,咱们来整理一下思路,若是你使用GROUP,那么随后的逻辑语句中可操做的列的数目将会减小,包括SELECT语句,这就是为何你在SELECT语句中只能引用GROUP BY语句产生的列的缘由。

  • 注意,其余列仍能够做为聚合函数的参数可用:
SELECT A.x, A.y, SUM(A.z)

FROM A

GROUP BY A.x, A.y

SELECT A.x, A.y, SUM(A.z)

FROM A

GROUP BY A.x, A.y
  • 不幸的是MySQL没有使用这个标准,所以也形成了一些问题。不要被MySQL戏弄了,GROUP BY会改变表引用的方式,您只能引用GROUP BY操做产生的列。

GROUP BY也是创建在表引用的基础上的,而且将它转化成了一个新表。

九、SQL的SELECT在关系代数中被称为投影(也称映射)

我我的更喜欢“投影”这个词,关系代数中就是用的这种说法。一旦你生成了新表(FROM)、而且通过了过滤(WHERE)、转化(GROUP BY),你就能够将结果投影到另外一张表上,SELECT语句就像一个投影仪:使用某种行值表达式将先前创建的表中的数据映射到最终的结果表中。

使用SELECT语句,你能够对每一列进行操做,构建复杂的列表达式。

一些经常使用表达式和函数都有不少特殊的使用规则,你应当记住如下这些:

  1. 只能引用输出表(以前操做产生的表)中的列。
  2. 若是你使用了GROUP BY语句,那么你只能引用GROUP BY操做产生的列,聚合函数除外。
  3. 若是你的程序中没有GROUP BY语句,你可使用窗口函数来代替聚合函数。
  4. 若是你的程序中没有GROUP BY,那么禁止同时使用聚合函数和非聚合函数。
  5. 在聚合函数中使用正则表达式也有一些特殊要求,反之亦然。
  6. 等等…

还有不少复杂的规则,足以用来写另外一个教程。例如,在没有GROUP BY的SELECT语句中不能将聚合函数与非聚合函数结合起来使用的缘由是:

  • 直觉告诉我,没意义。
  • 若是你没有这样的直觉(这对初学者来讲很难),那么让语法来告诉你:SQL 1999标准引入了GROUPING SETS,SQL 2003标准引入了空分组集:GROUP BY ()。当聚合函数出现而且没有GROUP BY时,一个隐含的,空的GROUPING SET会被使用。所以,原有的关于逻辑顺序的规则将再也不适用,即投影(SELECT)操做会先影响逻辑关系,而后影响到语法关系(GROUP BY)。

尽管看上去简单,SELECT语句实际上是SQL中最复杂的部分,其余操做都不过是引用完这个表引用那个表。SELECT彻底打乱了这些表,而且对它们应用了一些特殊的规则。

为了进一步了解SQL,应当在搞定SELECT语句以前掌握其余全部内容,尽管写程序时咱们把它放在最前面,但事实上这是最难啃的部分。

十、DISTINCT, UNION, ORDER BY, 和OFFSET相对来讲就容易不少了

  • 集合运算 (DISTINCT 和 UNION)
  • 排序操做 (ORDER BY, OFFSET .. FETCH)

集合运算:

集合运算是基于“集合”的操做,实际上,那就是……表!概念上也很容易理解:

  • DISTINCT :删除投影操做后的重复项
  • UNION:链接两个子查询而且删除重复项
  • UNION ALL : 链接两个子查询而且保留重复项
  • EXCEPT:删除第一个子查询中已在第二个子查询中存在的元素(而后删除重复项)
  • INTERSECT :只保留两个子查询中都存在的元素并删除重复项

一般来讲,这些被删除的重复项都是无心义的,大多数时候,使用UNION ALL来链接两个子查询就够了。

排序操做

排序不具备关系理论的特征,它是SQL特有的特征,一般应用在语法顺序和逻辑顺序的最后,使用ORDER BY、OFFSET .. FETCH是保证数据可以经过索引访问的惟一方式,其余排序都是任意和随机的。

OFFSET .. FETCH的语法规则略有不一样,其余的变体包括MySQL和PostgreSQL的 LIMIT .. OFFSET, SQL Server 和Sybase的 TOP .. START AT。更多关于OFFSET .. FETCH的内容能够看这里:

http://www.jooq.org/doc/3.1/manual/sql-building/sql-statements/select-statement/limit-clause/

相关文章
相关标签/搜索