全面了解SQL

不少程序员认为SQL是一头难以驯服的野兽。它是为数很少的声明性语言之一,也由于这样,其展现了彻底不一样于其余的表现形式、命令式语言、 面向对象语言甚至函数式编程语言(虽然有些人以为SQL 仍是有些相似功能)。java

  我天天都写SQL,个人开源软件JOOQ中也包含SQL。所以我以为有必要为还在为此苦苦挣扎的你呈现SQL的优美!下面的教程面向于:程序员

  • 已经使用过但没有彻底理解SQL的读者sql

  • 已经差很少了解SQL但从未真正考虑过它的语法的读者数据库

  • 想要指导他人学习SQL的读者编程

  本教程将重点介绍SELECT 语句。其余 DML 语句将在另外一个教程中在作介绍。接下来就是…安全

  一、SQL是声明性语言编程语言

  首先你须要思考的是,声明性。你惟一须要作的只是声明你想得到结果的性质,而不须要考虑你的计算机怎么算出这些结果的。ide

SELECT first_name, last_name FROM employees WHERE salary > 100000

  这很容易理解,你无须关心员工的身份记录从哪来,你只须要知道谁有着不错的薪水。函数式编程

  从中咱们学到了什么呢?函数

  那么若是它是这样的简单,会出现什么问题吗?问题就是咱们大多数人会直观地认为这是命令式编程。如:“机器,作这,再作那,但在这以前,若是这和那都发生错误,那么会运行一个检测”。这包括在变量中存储临时的编写循环、迭代、调用函数,等等结果。

  把那些都忘了吧,想一想怎么去声明,而不是怎么告诉机器去计算。

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

  常见的混乱源于一个简单的事实,SQL语法元素并不会按照它们的执行方式排序。语法顺序以下:

  • SELECT [DISTINCT]

  • FROM

  • WHERE

  • GROUP BY

  • HAVING

  • UNION

  • ORDER BY

  为简单起见,并无列出全部SQL语句。这个语法顺序与逻辑顺序基本上不一样,执行顺序以下: 

  • FROM

  • WHERE

  • GROUP BY

  • HAVING

  • SELECT

  • DISTINCT

  • UNION

  • ORDER BY

  这有三点须要注意:

  一、第一句是FROM,而不是SELECT。首先是将数据从磁盘加载到内存中,以便对这些数据进行操做。

  二、SELECT是在其余大多数语句后执行,最重要的是,在FROM和GROUP BY以后。重要的是要理解当你以为你能够从WHERE语句中引用你定义在SELECT语句当中的时候,。如下是不可行的:

SELECT A.x + A.y AS z FROM A WHERE z = 10 -- z is not available here!

  若是你想重用z,您有两种选择。要么重复表达式: 

SELECT A.x + A.y AS z FROM A WHERE (A.x + A.y) = 10

  或者你使用派生表、公用表表达式或视图来避免代码重复。请参阅示例进一步的分析:

  三、在语法和逻辑顺序里,UNION都是放在ORDER BY以前,不少人认为每一个UNION子查询均可以进行排序,但根据SQL标准和大多数的SQL方言,并非真的可行。虽然一些方言容许子查询或派生表排序,但却不能保证这种顺序能在UNION操做后保留。

  须要注意的是,并非全部的数据库都以相同的形式实现,例如规则2并不彻底适用于MySQL,PostgreSQL,和SQLite上

  从中咱们学到了什么呢?

  要时刻记住SQL语句的语法顺序和逻辑顺序来避免常见的错误。若是你能明白这其中的区别,就能明确知道为何有些能够执行有些则不能。

  若是能设计一种在语法顺序上实际又体现了逻辑顺序的语言就更好了,由于它是在微软的LINQ上实现的。

  三、SQL是关于数据表引用的 

  由于语法顺序和逻辑顺序的差别,大多数初学者可能会误认为SQL中列的值是第一重要的。其实并不是如此,最重要的是数据表引用。

  该SQL标准定义了FROM语句,以下:

<from clause> ::= FROM &lt;table reference&gt; [ { &lt;comma&gt; &lt;table reference&gt; }... ]

  ROM语句的"output"是全部表引用的结合程度组合表引用。让咱们慢慢地消化这些。 

FROM a, b

  上述产生一个a+b度的组合表引用,若是a有3列和b有5列,那么"输出表"将有8(3+5)列。

  包含在这个组合表引用的记录是交叉乘积/笛卡儿积的axb。换句话说,每一条a记录都会与每一条b记录相对应。若是a有3个记录和b有5条记录,而后上面的组合表引用将产生15条记录(3×5)。

  在WHERE语句筛选后,GROUP BY语句中"output"是"fed"/"piped",它已转成新的"output",咱们会稍后再去处理。

  若是咱们从关系代数/集合论的角度来看待这些东西,一个SQL表是一个关系或一组元素组合。每一个SQL语句将改变一个或几个关系,来产生新的关系。

  从中咱们学到了什么呢?

  一直从数据表引用角度去思考,有助于理解数据怎样经过你的sql语句流水做业的

  四、SQL数据表引用能够至关强大

  表引用是至关强大的东西。举个简单的例子,JOIN关键字其实不是SELECT语句的一部分,但倒是"special"表引用的一部分。链接表,在SQL标准中有定义(简化的):

复制代码
<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语句的一部分。某些数据库容许JOIN在插入、更新、删除中使用。

  五、应使用SQL JOIN的表,而不是以逗号分隔表 

  前面,咱们已经看到这语句: 

FROM a, b

  高级SQL开发人员可能会告诉你,最好不要使用逗号分隔的列表,而且一直完整的表达你的JOINs。这将有助于改进你的SQL语句的可读性从而防止错误出现。

  一个很是常见的错误是忘记某处链接谓词。思考如下内容:

复制代码
FROM a, b, c, d, e, f, g, h WHERE a.a1 = b.bx AND a.a2 = c.c1 AND d.d1 = b.bc -- etc...
复制代码

  使用join来查询表的语法

  • 更安全,你能够把链接谓词与链接表放一块儿,从而防止错误。

  • 更富于表现力,你能够区分外部链接,内部链接,等等。​​

  从中咱们学到了什么呢?

  使用JOIN,而且永远不在FROM语句中使用逗号分隔表引用。 

  六、SQL的不一样类型的链接操做

  链接操做基本上有五种

  • EQUI JOIN

  • SEMI JOIN

  • ANTI JOIN

  • CROSS JOIN

  • DIVISION

  这些术语一般用于关系代数。对上述概念,若是他们存在,SQL会使用不一样的术语。让咱们仔细看看:

  EQUI JOIN(同等链接)

  这是最多见的JOIN操做。它有两个子操做:

  • INNER JOIN(或者只是JOIN)

  • OUTER JOIN(能够再次拆分为LEFT, RIGHT,FULL OUTER JOIN)

  例子是其中的区别最好的解释:

复制代码
-- This table reference contains authors and their books. -- There is one record for each book and its author. -- authors without books are NOT included  author JOIN book ON author.id = book.author_id -- This table reference contains authors and their books -- There is one record for each book and its author. -- ... OR there is an "empty" record for authors without books -- ("empty" meaning that all book columns are NULL)  author LEFT OUTER JOIN book ON author.id = book.author_id
复制代码

  SEMI JOIN(半链接)

  这种关系的概念在SQL中用两种方式表达:使用IN谓词或使用EXISTS谓语。"Semi"是指在拉丁语中的"half"。这种类型的链接用 于链接只有"half"的表引用。再次考虑上述加入的做者和书。让咱们想象,咱们想要做者/书的组合,但只是那些做者实际上也有书。而后咱们能够这样写:

复制代码
-- Using IN FROM author WHERE author.id IN (SELECT book.author_id FROM book) -- Using EXISTS FROM author WHERE EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)
复制代码

  虽然不能确定你究竟是更加喜欢IN仍是EXISTS,并且也没有规则说明,但能够这样说:

  • IN每每比EXISTS更具可读性

  • EXISTS每每比IN更富表现力(如它更容易表达复杂的半链接)

  • 通常状况下性能上没有太大的差别,但,在某些数据库可能会有巨大的性能差别。

  由于INNER JOIN有可能只产生有书的做者,由于不少初学者可能认为他们可使用DISTINCT删除重复项。他们认为他们能够表达一个像这样的半联接:

-- Find only those authors who also have books SELECT DISTINCT first_name, last_name FROM author

  这是很是很差的作法,缘由有二:

  • 它很是慢,由于该数据库有不少数据加载到内存中,只是要再删除重复项。

  • 它不彻底正确,即便在这个简单的示例中它产生了正确的结果。可是,一旦你JOIN更多的表引用,,你将很难从你的结果中正确删除重复项。

  更多的关于DISTINCT滥用的问题,能够访问这里的博客

  ANTI JOIN(反链接)

  这个关系的概念跟半链接恰好相反。您能够简单地经过将 NOT 关键字添加到IN 或 EXISTS中生成它。在下例中,咱们选择那些没有任何书籍的做者:

复制代码
-- Using IN FROM author WHERE author.id NOT IN (SELECT book.author_id FROM book) -- Using EXISTS FROM author WHERE NOT EXISTS (SELECT 1 FROM book WHERE book.author_id = author.id)
复制代码

  一样的规则对性能、可读性、表现力都适用。然而,当使用NOT IN时对NULLs会有一个小警告,这个问题有点超出本教程范围。

  CROSS JOIN(交叉链接)

  结合第一个表中的内容和第二个表中的内容,引用两个join表交叉生成一个新的东西。咱们以前已经看到,这能够在FROM语句中经过逗号分隔表引用来实现。在你确实须要的状况下,能够在SQL语句中明确地写一个CROSS JOIN。

-- Combine every author with every book  author CROSS JOIN book

  DIVISION(除法)

  关系分割就是一只真正的由本身亲自喂养的野兽。简而言之,若是JOIN是乘法,那么除法就是JOIN的反义词。在SQL中,除法关系难以表达清楚。因为这是一个初学者的教程,解释这个问题超出了咱们的教程范围。固然若是你求知欲爆棚,那么就看这里这里还有这里

  从中咱们学到了什么呢?

  让咱们把前面讲到的内容再次牢记于心。SQL是表引用。链接表是至关复杂的表引用。但关系表述和SQL表述仍是有点区别的,并非全部的关系连 接操做都是正规的SQL链接操做。对关系理论有一点实践与认识,你就能够选择JOIN正确的关系类型并能将其转化为正确的SQL。

  七、SQL的派生表就像表的变量

  前文,咱们已经了解到SQL是一种声明性语言,所以不会有变量。(虽然在一些SQL语句中可能会存在)但你能够写这样的变量。那些野兽通常的表被称为派生表。

  派生表只不过是包含在括号里的子查询。

-- A derived table FROM (SELECT * FROM author)

  须要注意的是,一些SQL方言要求派生表有一个关联的名字(也被称为别名)。

-- A derived table with an alias 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标准里已将派生表带到下一级别,,引入公共表表达式。这将容许你在单一的SQL SELECT中重复使用相同的派生表。上面的查询将转化为相似这样的:

复制代码
WITH a AS ( SELECT first_name, last_name, current_date - date_of_birth age FROM author ) SELECT * FROM a WHERE age > 10000
复制代码

  很明显,对普遍重用的常见SQL子查询,你也能够灌输具体"a"到一个独立视图中。想要了解更多就看这里

  从中咱们学到了什么呢?

  再温习一遍,SQL主要是关于表引用,而不是列。好好利用这些表引用。不要惧怕写派生表或其余复杂的表引用。

  八、SQL GROUP BY转换以前的表引用

  让咱们从新考虑咱们以前的FROM语句:

FROM a, b

  如今,让咱们来应用一个GROUP BY语句到上述组合表引用

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

  这会产生一个只有其他三个列(!)的新的表引用。让咱们再消化一遍。若是你用了GROUP BY,那么你在全部后续逻辑条款-包括选择中减小可用列的数量。这就是为何你只能够从SELECT语句中的GROUP BY语句引用列语法的缘由。

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

 

  • 值得注意并很不幸的是,MySQL不遵照这一标准,只会形成混乱。不要陷入MySQL的把戏。GROUP BY转换表引用,所以你能够只引用列也引用GROUPBY语句。

从中咱们学到了什么呢?

GROUP BY,在表引用上操做,将它们转换成一个新表。

  九、SQL SELECT在关系代数中被称为投影

  当它在关系代数中使用时,我我的比较喜欢用"投影"一词中。一旦你生成你的表引用,过滤,转换它,你能够一步将它投影到另外一个表中。SELECT语句就像一个投影机。表函数利用行值表达式将以前构造的表引用的每一个记录转化为最终结果。

  在SELECT语句中,你终于能够在列上操做,建立复杂的列表达式做为记录/行的一部分。

  有不少关于可用的表达式,函数性质等的特殊规则。最重要的是,你应该记住这些:

  一、你只能使用从“output”表引用产生的列引用

  二、若是你有GROUP BY语句,你只可能从该语句或聚合函数引用列

  三、当你没有GROUP BY语句时,你能够用窗口函数替代聚合函数

  四、若是你没有GROUP BY语句,你就不能将聚合函数与非聚合函数结合起来

  五、这有一些关于在聚合函数包装常规函数的规则,反之亦然

  六、还有…

  嗯,这有不少复杂的规则。他们能够填补另外一个教程。例如,之因此你不能将聚合函数与非聚合函数结合起来投影到没有GROUP BY的SELECT语句中是由于:

  一、凭直觉,没有任何意义。

  二、对一个SQL初学者来讲,直觉仍是毫无帮助的,语法规则则能够。SQL:1999年引入了分组集,SQL:2003引入了空分组集 GROUP BY()。每当存在没有显式GROUP BY语句的聚合函数时就会应用隐式的空分组集(规则2)。所以,最初关于逻辑顺序的那个规则就不彻底正确了,SELECT的投影也会影响前面的逻辑结果, 但语法语句GROUP BY却不受影响。

是否是有点迷糊?其实我也是,让咱们看一些简单的吧。

  从中咱们学到了什么呢?

  在SQL语句中,SELECT语句多是最复杂的语句之一,即便它看起来是那么的简单。全部其余语句只不过是从这个到另外一个表引用的的输送管道。经过将它们彻底转化,后期地对它们应用一些规则,SELECT语句完彻底全地搅乱了这些表引用的美。

  为了了解SQL,重要的是要理解其余的一切,都要尝试在SELECT以前解决。即使SELECT在语法顺序中排第一的语句,也应该将它放到最后。

  10.相对简单一点的SQL DISTINCT,UNION,ORDER BY,和OFFSET

  看完复杂的SELECT以后,咱们看回一些简单的东西。

  • 集合运算(DISTINCT和UNION)

  • 排序操做(ORDER BY,OFFSET..FETCH)

  集合运算

  集合运算在除了表其实没有其余东西的“集”上操做。嗯,差很少是这样,从概念上讲,它们仍是很容易理解的

  • DISTINCT投影后删除重复项。

  • UNION求并集,删除重复项。

  • UNION ALL求并集,保留重复项。

  • EXCEPT求差集(在第一个子查询结果中删除第二个子查询中也含有的记录删除),删除重复项。

  • INTERSECT求交集(保留全部子查询都含有的记录),删除重复项。

  全部这些删除重复项一般是没有意义的,不少时候,当你想要链接子查询时,你应该使用UNION ALL。

  排序操做

  排序不是一个关系特征,它是SQL仅有的特征。在你的SQL语句中,它被应用在语法排序和逻辑排序以后。保证能够经过索引访问记录的惟一可靠方法是使用ORDER BY a和OFFSET..FETCH。全部其余的排序老是任意的或随机的,即便它看起来像是可再现的。

  OFFSET..FETCH是惟一的语法变体。其余变体包括MySQL'和PostgreSQL的LIMIT..OFFSET,或者SQL Server和Sybase的TOP..START AT(这里)。

  让咱们开始应用吧

  跟其余每一个语言同样,要掌握SQL语言须要大量的实践。上述10个简单的步骤将让你天天编写SQL时更有意义。另外一方面,你也能够从常见的错误中学习到更多。下面的两篇文章列出许多Java(和其余)开发者写SQL时常见的错误:

  · 10 Common Mistakes Java Developers Make when Writing SQL

  · 10 More Common Mistakes Java Developers Make when Writing SQL

相关文章
相关标签/搜索