#SQLこのコラムでは、SQL を完全に理解するための 10 のステップを紹介します
推奨(無料): SQL
多くのプログラマーは SQL を災難だと考えています。 SQL は、私たちがよく知っているコマンドライン言語、オブジェクト指向プログラミング言語、さらには関数型言語とはまったく異なる動作をする数少ない宣言型言語の 1 つです (ただし、SQL 言語も関数型言語であると考える人もいます) )。 私たちは毎日 SQL を作成し、それをオープンソース ソフトウェア jOOQ に適用します。そこで、まだ SQL について頭を悩ませている友人たちに SQL の素晴らしさを紹介したいと思い、この記事は特に次の読者向けに書きました:1. 仕事で SQL を使用しますが、SQL は使用しません。それについてあまり知らない、完全に理解していない人々。
2. SQL の使用に習熟しているが、その文法ロジックを理解していない人。
3. 他の人に SQL を教えたい人。
この記事では SELECT 文型に焦点を当て、他の DML (Data Manipulation Language データ操作言語コマンド) については別の記事で紹介します。SQL を完全に理解するための 10 の簡単なステップ
1. SQL は宣言型言語です
まず、この概念を維持してください。念頭に置いているのは「ステートメント」。 SQL 言語は、結果を取得する方法をコンピュータに指示するのではなく、生データからどのような結果を取得したいかをコンピュータに宣言する例です。これは素晴らしいことではないでしょうか? (翻訳者注: 簡単に言うと、SQL 言語は結果セットのプロパティを宣言します。コンピュータは、従来のプログラミングの考え方ではなく、SQL が宣言した内容に基づいて、データベースから宣言を満たすデータを選択します。コンピューターに操作方法を指示するため。)SELECT first_name, last_name FROM employees WHERE salary > 100000上の例は理解しやすいです。これらの従業員の記録がどこから来たのかは気にしません。必要なのは、高給取りの従業員のデータだけです (翻訳者注) : 給与>100000) 。
これをどこで知りましたか?
SQL 言語がそれほど単純であるなら、人々が「SQL を聞くと青ざめる」のはなぜでしょうか?その主な理由は、私たちが無意識のうちに命令型プログラミングの考え方に従って問題を考えているからです。これは次のようなものです。「コンピュータ、最初にこのステップを実行し、次にそのステップを実行します。ただし、その前に、条件 A と条件 B が満たされているかどうかを確認してください。」たとえば、変数を使用してパラメータを渡すこと、ループ ステートメントを使用すること、反復処理、関数の呼び出しなどはすべて、この命令型プログラミングの思考習慣です。2. SQL 構文は構文上の順序で実行されない
SQL ステートメントには、ほとんどの人を混乱させる特徴があります。それは、SQL ステートメントの実行順序です。ステートメントの文法的順序と矛盾しています。 SQL ステートメントの構文シーケンスは次のとおりです:SELECT A.x + A.y AS z FROM A WHERE z = 10 -- z 在此处不可用,因为SELECT是最后执行的语句!エイリアス z を再利用したい場合、2 つのオプションがあります。 z:
SELECT A.x + A.y AS z FROM A WHERE (A.x + A.y) = 10... で表される式を書き直すか、派生テーブル、共通データ式、またはビューを使用してエイリアスの再利用を回避してください。以下の例を参照してください。 3. 構文でも実行順序でも、UNION は常に ORDER BY より前にランクされます。多くの人は、すべての UNION セグメントが ORDER BY を使用してソートできると考えていますが、SQL 言語標準と各データベースの SQL 実装の違いによると、これは真実ではありません。一部のデータベースでは、SQL ステートメントでサブクエリまたは派生テーブルを並べ替えることができますが、これは、UNION 操作後も並べ替えが並べ替えられた順序で維持されることを意味するものではありません。 注: すべてのデータベースが SQL ステートメントに同じ解析方法を使用するわけではありません。たとえば、MySQL、PostgreSQL、SQLite は、上記の 2 番目の点で述べたように動作しません。
私たちは何を学んだのでしょうか?
既然并不是所有的数据库都按照上述方式执行 SQL 预计,那我们的收获是什么?我们的收获是永远要记得:SQL 语句的语法顺序和其执行顺序并不一致,这样我们就能避免一般性的错误。如果你能记住 SQL 语句语法顺序和执行顺序的差异,你就能很容易的理解一些很常见的 SQL 问题。
当然,如果一种语言被设计成语法顺序直接反应其语句的执行顺序,那么这种语言对程序员是十分友好的,这种编程语言层面的设计理念已经被微软应用到了 LINQ 语言中。
3、 SQL 语言的核心是对表的引用(table references)
由于 SQL 语句语法顺序和执行顺序的不同,很多同学会认为SELECT 中的字段信息是 SQL 语句的核心。其实真正的核心在于对表的引用。
根据 SQL 标准,FROM 语句被定义为:
<from clause> ::= FROM <table reference> [ { <comma> <table reference> }... ]
FROM 语句的“输出”是一张联合表,来自于所有引用的表在某一维度上的联合。我们们慢慢来分析:
FROM a, b
上面这句 FROM 语句的输出是一张联合表,联合了表 a 和表 b 。如果 a 表有三个字段, b 表有 5 个字段,那么这个“输出表”就有 8 ( =5+3)个字段。
这个联合表里的数据是 ab,即 a 和 b 的笛卡尔积。换句话说,也就是 a 表中的每一条数据都要跟 b 表中的每一条数据配对。如果 a 表有3 条数据, b 表有 5 条数据,那么联合表就会有 15 ( =53)条数据。
FROM 输出的结果被 WHERE 语句筛选后要经过 GROUP BY 语句处理,从而形成新的输出结果。我们后面还会再讨论这方面问题。
如果我们从集合论(关系代数)的角度来看,一张数据库的表就是一组数据元的关系,而每个 SQL 语句会改变一种或数种关系,从而产生出新的数据元的关系(即产生新的表)。
我们学到了什么?
思考问题的时候从表的角度来思考问题提,这样很容易理解数据如何在 SQL 语句的“流水线”上进行了什么样的变动。
4、 灵活引用表能使 SQL 语句变得更强大
灵活引用表能使 SQL 语句变得更强大。一个简单的例子就是 JOIN 的使用。严格的说 JOIN 语句并非是 SELECT 中的一部分,而是一种特殊的表引用语句。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 个字段了。
(译者注:原文这里用词为 degree ,译为维度。如果把一张表视图化,我们可以想象每一张表都是由横纵两个维度组成的,横向维度即我们所说的字段或者列,英文为columns;纵向维度即代表了每条数据,英文为 record ,根据上下文,作者这里所指的应该是字段数。)
在 SQL 语句中派生表的引用甚至比表连接更加强大,下面我们就要讲到表连接。
我们学到了什么?
思考问题时,要从表引用的角度出发,这样就很容易理解数据是怎样被 SQL 语句处理的,并且能够帮助你理解那些复杂的表引用是做什么的。
更重要的是,要理解 JOIN 是构建连接表的关键词,并不是 SELECT 语句的一部分。有一些数据库允许在 INSERT 、 UPDATE 、 DELETE 中使用 JOIN 。
5、 SQL 语句中推荐使用表连接
我们先看看刚刚这句:
FROM a, b
高级 SQL 程序员也许学会给你忠告:尽量不要使用逗号来代替 JOIN 进行表的连接,这样会提高你的 SQL 语句的可读性,并且可以避免一些错误。
利用逗号来简化 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 语句中不同的连接操作
SQL 语句中,表连接的方式从根本上分为五种:
EQUI JOIN
这是一种最普通的 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
这种连接关系在 SQL 中有两种表现方式:使用 IN,或者使用 EXISTS。“ SEMI ”在拉丁文中是“半”的意思。这种连接方式是只连接目标表的一部分。这是什么意思呢?再想一下上面关于作者和书名的连接。我们想象一下这样的情况:我们不需要作者 / 书名这样的组合,只是需要那些在书名表中的书的作者信息。那我们就能这么写:
-- 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 ,但是这些事情你还是应该知道的:
因为使用 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
这是一种很糟糕的写法,原因如下:
ANTI JOIN
这种连接的关系跟 SEMI JOIN 刚好相反。在 IN 或者 EXISTS 前加一个 NOT 关键字就能使用这种连接。举个例子来说,我们列出书名表里没有书的作者:
-- 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)
关于性能、可读性、表达性等特性也完全可以参考 SEMI JOIN。
这篇博文介绍了在使用 NOT IN 时遇到 NULL 应该怎么办,因为有一点背离本篇主题,就不详细介绍,有兴趣的同学可以读一下
CROSS JOIN
这个连接过程就是两个连接的表的乘积:即将第一张表的每一条数据分别对应第二张表的每条数据。我们之前见过,这就是逗号在 FROM 语句中的用法。在实际的应用中,很少有地方能用到 CROSS JOIN,但是一旦用上了,你就可以用这样的 SQL语句表达:
-- Combine every author with every book author CROSS JOIN book
pISION
pISION 的确是一个怪胎。简而言之,如果 JOIN 是一个乘法运算,那么 pISION 就是 JOIN 的逆过程。pISION 的关系很难用 SQL 表达出来,介于这是一个新手指南,解释 pISION 已经超出了我们的目的。
我们学到了什么?
学到了很多!让我们在脑海中再回想一下。SQL 是对表的引用, JOIN 则是一种引用表的复杂方式。但是 SQL 语言的表达方式和实际我们所需要的逻辑关系之间是有区别的,并非所有的逻辑关系都能找到对应的 JOIN 操作,所以这就要我们在平时多积累和学习关系逻辑,这样你就能在以后编写 SQL 语句中选择适当的 JOIN 操作了。
7、 SQL 中如同变量的派生表
在这之前,我们学习到过 SQL 是一种声明性的语言,并且 SQL 语句中不能包含变量。但是你能写出类似于变量的语句,这些就叫做派生表:
说白了,所谓的派生表就是在括号之中的子查询:
-- A derived table FROM (SELECT * FROM author)
需要注意的是有些时候我们可以给派生表定义一个相关名(即我们所说的别名)。
-- 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 :1990 标准中,派生表被归为下一级——通用表语句( common table experssion)。这就允许你在一个 SELECT 语句中对派生表多次重用。上面的例子就(几乎)等价于下面的语句:
WITH a AS ( SELECT first_name, last_name, current_date - date_of_birth age FROM author ) SELECT * FROM a WHERE age > 10000
当然了,你也可以给“ 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 后面。(译者注:原文大意为“当你是用 GROUP BY 的时候,你能够对其进行下一级逻辑操作的列会减少,包括在 SELECT 中的列”)。
需要注意的是:其他字段能够使用聚合函数:
SELECT A.x, A.y, SUM(A.z) FROM A GROUP BY A.x, A.y
还有一点值得留意的是:MySQL 并不坚持这个标准,这的确是令人很困惑的地方。(译者注:这并不是说 MySQL 没有 GROUP BY 的功能)但是不要被 MySQL 所迷惑。GROUP BY 改变了对表引用的方式。你可以像这样既在 SELECT 中引用某一字段,也在 GROUP BY 中对其进行分组。
我们学到了什么?
GROUP BY,再次强调一次,是在表的引用上进行了操作,将其转换为一种新的引用方式。
9、 SQL 语句中的 SELECT 实质上是对关系的映射
我个人比较喜欢“映射”这个词,尤其是把它用在关系代数上。(译者注:原文用词为 projection ,该词有两层含义,第一种含义是预测、规划、设计,第二种意思是投射、映射,经过反复推敲,我觉得这里用映射能够更直观的表达出 SELECT 的作用)。一旦你建立起来了表的引用,经过修改、变形,你能够一步一步的将其映射到另一个模型中。SELECT 语句就像一个“投影仪”,我们可以将其理解成一个将源表中的数据按照一定的逻辑转换成目标表数据的函数。
通过 SELECT语句,你能对每一个字段进行操作,通过复杂的表达式生成所需要的数据。
SELECT 语句有很多特殊的规则,至少你应该熟悉以下几条:
一些更复杂的规则多到足够写出另一篇文章了。比如:为何你不能在一个没有 GROUP BY 的 SELECT 语句中同时使用普通函数和聚合函数?(上面的第 4 条)
原因如下:
糊涂了?是的,我也是。我们再回过头来看点浅显的东西吧。
我们学到了什么?
SELECT 语句可能是 SQL 语句中最难的部分了,尽管他看上去很简单。其他语句的作用其实就是对表的不同形式的引用。而 SELECT 语句则把这些引用整合在了一起,通过逻辑规则将源表映射到目标表,而且这个过程是可逆的,我们可以清楚的知道目标表的数据是怎么来的。
想要学习好 SQL 语言,就要在使用 SELECT 语句之前弄懂其他的语句,虽然 SELECT 是语法结构中的第一个关键词,但它应该是我们最后一个掌握的。
10、 SQL 语句中的几个简单的关键词:DISTINCT , UNION , ORDER BY 和 OFFSET
在学习完复杂的 SELECT 豫剧之后,我们再来看点简单的东西:
集合运算( DISTINCT 和 UNION )
排序运算( ORDER BY,OFFSET…FETCH)
集合运算( set operation):
集合运算主要操作在于集合上,事实上指的就是对表的一种操作。从概念上来说,他们很好理解:
DISTINCT 在映射之后对数据进行去重
UNION 将两个子查询拼接起来并去重
UNION ALL 将两个子查询拼接起来但不去重
EXCEPT 最初のサブクエリから 2 番目のサブクエリの結果を削除します。
INTERSECT 両方のサブクエリの結果を保持し、重複を削除します。
順序付け操作操作):
並べ替え操作は論理関係とは何の関係もありません。これは SQL 固有の機能です。ソート操作は SQL ステートメントの最後に実行されるだけでなく、SQL ステートメントの実行中の最後にも実行されます。 ORDER BY と OFFSET...FETCH を使用することは、データを順序どおりに配置できるようにする最も効果的な方法です。他のすべての並べ替え方法はある程度ランダムですが、生成される並べ替え結果は再現可能です。
OFFSET…SET は構文が統一されていないステートメントであり、MySQL や PostgreSQL の LIMIT…OFFSET、SQL Server や Sybase の TOP…START AT など、データベースごとに表現が異なります。
仕事で SQL を楽しんでみましょう!
他の言語と同様、SQL 言語をしっかり学びたい場合は、たくさんの練習が必要です。上記の 10 の簡単な手順は、毎日作成する SQL ステートメントをより深く理解するのに役立ちます。その一方で、よくある間違いから多くの経験を蓄積することができます。
以上がSQLを完全に理解するための10のステップを紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。