简单十步让你全面理解SQL

很多程序员认为SQL是一头难以驯服的野兽。它是为数不多的声明性语言之一,也因为这样,其展示了完全不同于其他的表现形式、命令式语言、 面向对象语言甚至函数式编程语言(虽然有些人觉得SQL 还是有些类似功能)。

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

  已经使用过但没有完全理解SQL的读者

  已经差不多了解SQL但从未真正考虑过它的语法的读者

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

  本教程将重点介绍SELECT 语句。其他 DML 语句将在另一个教程中在做介绍。接下来就是…

  1、SQL是声明性语言

  首先你需要思考的是,声明性。你唯一需要做的只是声明你想获得结果的性质,而不需要考虑你的计算机怎么算出这些结果的。

  SELECT first_name, last_name FROM employees WHERE salary > 100000

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

  从中我们学到了什么呢?

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

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

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

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


SELECT [DISTINCT]

FROM

WHERE

GROUP BY

HAVING

UNION

ORDER BY

  为简单起见,并没有列出所有SQL语句。这个语法顺序与逻辑顺序基本上不同,执行顺序如下:


FROM

WHERE

GROUP BY

HAVING

SELECT

DISTINCT

UNION

ORDER BY

  这有三点需要注意:

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

  2、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

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

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

  需要注意的是,并不是所有的数据库都以相同的形式实现,例如规则2并不完全适用于MySQL,PostgreSQL,和SQLite上

  从中我们学到了什么呢?

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

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

  3、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语句流水作业的

  4、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在插入、更新、删除中使用。

  5、应使用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语句中使用逗号分隔表引用。

  6、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。

 7、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主要是关于表引用,而不是列。好好利用这些表引用。不要害怕写派生表或其他复杂的表引用。

  8、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,在表引用上操作,将它们转换成一个新表。

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

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

  在SELECT语句中,你终于可以在列上操作,创建复杂的列表达式作为记录/行的一部分。

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

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

  2、如果你有GROUP BY语句,你只可能从该语句或聚合函数引用列

  3、当你没有GROUP BY语句时,你可以用窗口函数替代聚合函数

  4、如果你没有GROUP BY语句,你就不能将聚合函数与非聚合函数结合起来

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

  6、还有…

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

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

  2、对一个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

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-10-07 18:14:37

简单十步让你全面理解SQL的相关文章

十步完全理解SQL(转)

本文由 伯乐在线 - 水果泡腾片 翻译.未经许可,禁止转载!英文出处: Lukas Eder .欢迎加入翻译组. 很多程序员视 SQL 为洪水猛兽.SQL 是一种为数不多的声明性语言,它的运行方式完全不同于我们所熟知的命令行语言.面向对象的程序语言.甚至是函数语言(尽管有些人认为 SQL 语言也是一种函数式语言). 我们每天都在写 SQL 并且应用在开源软件 jOOQ 中.于是我想把 SQL 之美介绍给那些仍然对它头疼不已的朋友,所以本文是为了以下读者而特地编写的: 1. 在工作中会用到 SQL

DIV CSS网页布局实例:十步学会用CSS建站

css|网页 Update:本篇已得到原作者Steve Dennis的翻译准予,在此Jorux表示感谢! 本教程主要参考Creating a CSS Layout from scratch,由Jorux翻译,以意译为主,其间加入了不少Jorux的个人观点,省略了一些多余的说明,请读者明鉴. 目录: 第一步:规划网站,本教程将以图示为例构建网站: 第二步:创建html模板及文件目录等: 第三步:将网站分为五个div,网页基本布局的基础: 第四步:网页布局与div浮动等: 第五步:网页主要框架之外的

理解SQL SERVER中的逻辑读,预读和物理读

SQL SERVER数据存储的形式 在谈到几种不同的读取方式之前,首先要理解SQL SERVER数据存储的方式.SQL SERVER存储的最小单位为页(Page).每一页大小为8k,SQL SERVER对于页的读取是原子性,要么读完一页,要么完全不读,不会有中间状态.而页之间的数据组织结构为B树.所以SQL SERVER对于逻辑读,预读,和物理读的单位是页. SQL SERVER一页的总大小为:8K 但是这一页存储的数据会是:8K=8192字节-96字节(页头)-36字节(行偏移)=8060字节

理解SQL SERVER中的逻辑读,预读和物理读_MsSql

SQL SERVER数据存储的形式       在谈到几种不同的读取方式之前,首先要理解SQL SERVER数据存储的方式.SQL SERVER存储的最小单位为页(Page).每一页大小为8k,SQL SERVER对于页的读取是原子性,要么读完一页,要么完全不读,不会有中间状态.而页之间的数据组织结构为B树(请参考我之前的博文).所以SQL SERVER对于逻辑读,预读,和物理读的单位是页.             SQL SERVER一页的总大小为:8K       但是这一页存储的数据会是:

十步之遥顺利升级到Visual Studio 2008

升级到Visual Studio 2008,来个飞跃.Visual Studio 2008最近发布了,开发者经常希望自己成为安装他们最青睐开发工具最新版本的第一人.但是,若在升级过程中做出了错误的选择的话,就会为之付出一些代价.由于Jon Galloway等人,确保了升级过程是一个平稳的过程,而且只有十步之遥. 1. 首先,小心的卸载所有先前发布的相应软件和模块 这是最重要的步骤之一.如果您有任何先前版本的.NET Framework 3.5或Visual Studio 2008,那么这些软件都

VC大佬支招:IPO十步走至少20%时间教育投资人

利用资本运作成长的技术型公司,从成立到上市需要花费 8-10 年时间.没有一夕成功的上市,从创立到上市是一场需要投入海量工作和艰辛努力的马拉松.顺利上市过程中隐藏着巨大的风险,创业者需要有充分的准备才能确保万无一失.而万全的准备要求你具备预见性.对未来的眼界和巨大的增长潜力,同时不容许丝毫的败笔.即使公司已具备了上述属性,你的首次公开募股仍不能算胜券在握.不妨考虑以下十步走的计划来确保一个万无一失的上市进程.你需要一个既懂商业模式也深谙华尔街运作规则的CFO如果公司还没有这号人才,那么得至少在你

教你简单10步大幅提升网站可访问性

  第一步 检查<title></title>. 不允许空,不允许过长,简洁明了.<title></title>是第一个可以访问到内容的元素,所以一定要非常重视.当用户切换浏览器Tab标签的时候,一定最先听到<title></title>标记的内容.Title一定要能代表当前页面的主题.这里的要求和SEO最佳实践几乎一致. 第二步 提供文字替代方案. 检查网页上所有的图片.iframe.object,检查这些元素是否填写了适当的alt

十步搭建 OpenVPN,享受你的隐私生活

十步搭建 OpenVPN,享受你的隐私生活 我们支持保护隐私,不为我们有自己的秘密需要保护,只是我们认为保护隐私应该成为一项基本人权.所以我们坚信无论谁在什么时候行使这项权利,都应该不受拘束的获取必须的工具和服务.OpenVPN就是这样一种服务并且有多种工具(客户端) 来让我们利用并享受这种服务. 通过与一个OpenVPN服务器建立连接,我们基本上在我们的设备和远端运行OpenVPN的主机之间建立了一个安全的通信通道.尽管在两个端点之间的通信可能被截获,但是信息是经过高强度加密的所以实际上它对于

《新理财》:战略内审十步走

高效的内部审计职能,将对推动企业的风险管理.内部控制建设和可持续发展起到关键性作用.上期本刊介绍了建立战略性内审职能的十步启动框架中的第1-4步,本期继续介绍第5-7步. ⊙ 魏宝星/文 在建立了内审战略框架后,工作的重点将转移到战术执行上.如图1所示,通过实施第5~10步的职能和活动,内审工作将立见成效,并取得长期的成功. 第五步:编制当前和长期的预算 完成框架的第1??4步后,可得到足够的信息去建立当前和长期的预算.预算必须为内审提供足够的资源,以执行第4步制定的基于风险的审计计划,并应有适