多くのプログラマーは SQL を災難だと考えています。 SQL は、私たちがよく知っているコマンドライン言語、オブジェクト指向プログラミング言語、さらには関数型言語とはまったく異なる動作をする数少ない宣言型言語の 1 つです (ただし、SQL 言語も関数型言語であると考える人もいます) )。
私たちは毎日 SQL を書き、それをオープンソース ソフトウェア jOOQ に適用しています。そこで、まだ SQL について頭を悩ませている友人たちに SQL の素晴らしさを紹介したいと思います。そのため、この記事は特に次の読者を対象に書きました:
1. 仕事で SQL を使用する予定だが、十分に理解していない人。
2. SQL の使用に習熟しているが、その構文とロジックを理解していない人。
3. 他の人に SQL を教えたい人。
この記事では SELECT 文型に焦点を当て、他の DML (データ操作言語) コマンドについては他の記事で紹介します。
まず、「宣言」という概念を念頭に置いておかなければなりません。 SQL 言語は、結果を取得する方法をコンピュータに指示するのではなく、生データからどのような結果を取得したいかをコンピュータに宣言する例です。これは素晴らしいことではないでしょうか?
(翻訳者注: 簡単に言えば、SQL 言語は結果セットのプロパティを宣言します。コンピューターは、従来のプロパティの使用方法をコンピューターに指示するのではなく、SQL で宣言された内容に基づいて、データベースから宣言を満たすデータを選択します。プログラミング的思考。)
SELECT first_name, last_name FROM employees WHERE salary > 100000
上の例は、これらの従業員の記録がどこから来たのかは気にしません (翻訳者注: 給与>100,000)。 。
これをどこで学びましたか?
SQL 言語がそれほど単純であるなら、人々が「SQL を聞くと青ざめる」のはなぜでしょうか?その主な理由は、私たちが無意識のうちに命令型プログラミングの考え方に従って問題を考えているからです。これは次のようなものです。「コンピュータ、最初にこのステップを実行し、次にそのステップを実行します。ただし、その前に、条件 A と条件 B が満たされているかどうかを確認してください。」たとえば、変数を使用してパラメータを渡すこと、ループ ステートメントを使用すること、反復処理、関数の呼び出しなどはすべて、この命令型プログラミングの思考習慣です。
SQL ステートメントには、ほとんどの人を混乱させる特徴があります。それは、SQL ステートメントの実行順序がステートメントの文法的な順序と一致しないことです。 SQL ステートメントの構文順序は次のとおりです:
SELECT[DISTINCT]
FROM
WHERE
GROUP BY
HAVING
UNION
注文方法
理解を容易にするために、すべての SQL 構文構造が上にリストされているわけではありませんが、SQL ステートメントの構文順序がその実行順序と完全に異なることを示すのに十分です。上記のステートメントを例にとると、実行順序は次のとおりです。 :
FROM
WHERE
HAVING
SELECT
DISTINCT
でグループ化UNION
ORDER BY
実行についてSQL ステートメントの順序については、注目に値することが 3 つあります:
1. FROM は SQL ステートメント実行の最初のステップであり、SELECT ではありません。データベースが SQL ステートメントを実行するための最初のステップは、データを操作できるようにハードディスクからデータ バッファーにデータをロードすることです。 (訳者注:原文は「そのようなデータを操作するために、最初に起こるのはディスクからメモリにデータをロードすることです。」ですが、そうではありません。Oracleなどの一般的に使用されるデータベースを例にとります) 、データはハードディスクから抽出されます。)
2. SELECT は、ほとんどのステートメントが実行された後に実行されます。厳密には、FROM と GROUP BY の後に実行されます。これを理解することは非常に重要です。そのため、SELECT でエイリアス化されているフィールドを WHERE で条件として使用することはできません。
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 AWHERE (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 语言中。
由于 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 语句的“流水线”上进行了什么样的变动。
灵活引用表能使 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 。
我们先看看刚刚这句话:
FROM a, b
高级 SQL 程序员也许学会给你忠告:尽量不要使用逗号来代替 JOIN 进行表的连接,这样会提高你的 SQL 语句的可读性,并且可以避免一些错误。
利用逗号来简化 SQL 语句有时候会造成思维上的混乱,想一下下面的语句:
FROM a, b, c, d, e, f, g, h WHERE a.a1 = b.bxAND a.a2 = c.c1AND d.d1 = b.bc -- etc...
我们不难看出使用 JOIN 语句的好处在于:
安全。 JOIN 和要连接的表离得非常近,这样就能避免错误。
更多连接的方式,JOIN 语句能去区分出来外连接和内连接等。
我们学到了什么?
记着要尽量使用 JOIN 进行表的连接,永远不要在 FROM 后面使用逗号连接表。
SQL 语句中,表连接的方式从根本上分为五种:
EQUI JOIN
SEMI JOIN
ANTI JOIN
CROSS JOIN
DIVISION
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 ”在拉丁文中是“半”的意思。这种连接方式是只连接目标表的一部分。这是什么意思呢?再想一下上面关于作者和书名的连接。我们想象一下这样的情况:我们不需要作者 / 书名这样的组合,只是需要那些在书名表中的书的作者信息。那我们就能这么写:
-- 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 进行去重,然后将 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
这是一种很糟糕的写法,原因如下:
SQL 语句性能低下:因为去重操作( DISTINCT )需要数据库重复从硬盘中读取数据到内存中。(译者注: DISTINCT 的确是一种很耗费资源的操作,但是每种数据库对于 DISTINCT 的操作方式可能不同)。
这么写并非完全正确:尽管也许现在这么写不会出现问题,但是随着 SQL 语句变得越来越复杂,你想要去重得到正确的结果就变得十分困难。
更多的关于滥用 DISTINCT 的危害可以参考这篇博文
(http://blog.jooq.org/2013/07/30/10-common-mistakes-java-developers-make-when-writing-sql/)。
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 应该怎么办,因为有一点背离本篇主题,就不详细介绍,有兴趣的同学可以读一下
(http://blog.jooq.org/2012/01/27/sql-incompatibilities-not-in-and-null-values/)。
CROSS JOIN
这个连接过程就是两个连接的表的乘积:即将第一张表的每一条数据分别对应第二张表的每条数据。我们之前见过,这就是逗号在 FROM 语句中的用法。在实际的应用中,很少有地方能用到 CROSS JOIN,但是一旦用上了,你就可以用这样的 SQL语句表达:
author CROSS JOIN book
DIVISION
DIVISION 的确是一个怪胎。简而言之,如果 JOIN 是一个乘法运算,那么 DIVISION 就是 JOIN 的逆过程。DIVISION 的关系很难用 SQL 表达出来,介于这是一个新手指南,解释 DIVISION 已经超出了我们的目的。但是有兴趣的同学还是可以来看看这三篇文章
(http://blog.jooq.org/2012/03/30/advanced-sql-relational-pision-in-jooq/)
(http://en.wikipedia.org/wiki/Relational_algebra#Division)
(https://www.simple-talk.com/sql/t-sql-programming/pided-we-stand-the-sql-of-relational-pision/)。
我们学到了什么?
学到了很多!让我们在脑海中再回想一下。 SQL 是对表的引用, JOIN 则是一种引用表的复杂方式。但是 SQL 语言的表达方式和实际我们所需要的逻辑关系之间是有区别的,并非所有的逻辑关系都能找到对应的 JOIN 操作,所以这就要我们在平时多积累和学习关系逻辑,这样你就能在以后编写 SQL 语句中选择适当的 JOIN 操作了。
在这之前,我们学习到过 SQL 是一种声明性的语言,并且 SQL 语句中不能包含变量。但是你能写出类似于变量的语句,这些就叫做派生表:
说白了,所谓的派生表就是在括号之中的子查询:
-- A derived table FROM (SELECT * FROM author)
需要注意的是有些时候我们可以给派生表定义一个相关名(即我们所说的别名)。
-- A derived table with an aliasFROM (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 ”创建一个单独的视图,这样你就可以在更广泛的范围内重用这个派生表了。更多信息可以阅读下面的文章(http://en.wikipedia.org/wiki/View_%28SQL%29)。
我们学到了什么?
我们反复强调,大体上来说 SQL 语句就是对表的引用,而并非对字段的引用。要好好利用这一点,不要害怕使用派生表或者其他更复杂的语句。
让我们再回想一下之前的 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 AGROUP BY A.x, A.y
还有一点值得留意的是: MySQL 并不坚持这个标准,这的确是令人很困惑的地方。(译者注:这并不是说 MySQL 没有 GROUP BY 的功能)但是不要被 MySQL 所迷惑。 GROUP BY 改变了对表引用的方式。你可以像这样既在 SELECT 中引用某一字段,也在 GROUP BY 中对其进行分组。
我们学到了什么?
GROUP BY,再次强调一次,是在表的引用上进行了操作,将其转换为一种新的引用方式。
我个人比较喜欢“映射”这个词,尤其是把它用在关系代数上。(译者注:原文用词为 projection ,该词有两层含义,第一种含义是预测、规划、设计,第二种意思是投射、映射,经过反复推敲,我觉得这里用映射能够更直观的表达出 SELECT 的作用)。一旦你建立起来了表的引用,经过修改、变形,你能够一步一步的将其映射到另一个模型中。 SELECT 语句就像一个“投影仪”,我们可以将其理解成一个将源表中的数据按照一定的逻辑转换成目标表数据的函数。
通过 SELECT语句,你能对每一个字段进行操作,通过复杂的表达式生成所需要的数据。
SELECT 语句有很多特殊的规则,至少你应该熟悉以下几条:
你仅能够使用那些能通过表引用而得来的字段;
如果你有 GROUP BY 语句,你只能够使用 GROUP BY 语句后面的字段或者聚合函数;
当你的语句中没有 GROUP BY 的时候,可以使用开窗函数代替聚合函数;
当你的语句中没有 GROUP BY 的时候,你不能同时使用聚合函数和其它函数;
有一些方法可以将普通函数封装在聚合函数中;
……
一些更复杂的规则多到足够写出另一篇文章了。比如:为何你不能在一个没有 GROUP BY 的 SELECT 语句中同时使用普通函数和聚合函数?(上面的第 4 条)
原因如下:
直感的には、このアプローチは論理的には意味がありません。
直感が納得できないとしても、文法を見れば納得できるはずです。 SQL: 1999 標準では GROUPING SETS が導入され、SQL: 2003 標準ではグループ セットである GROUP BY() が導入されました。ステートメントに集計関数があり、明示的な GROUP BY ステートメントがない場合は、あいまいな空の GROUPING SET が SQL に適用されます。したがって、元の論理順序規則が壊れ、マッピング (つまり SELECT) 関係がまず論理関係に影響し、次に文法関係に影響します。 (訳者注: この段落の原文はかなり難しいですが、次のように理解すると簡単です。集約関数と通常の関数の両方を含む SQL ステートメントでは、グループ化のための GROUP BY が存在しない場合、SQL ステートメントはデフォルトでは、テーブル全体がグループとして集計されます。集計関数が特定のフィールドに対して集計統計を実行すると、参照されるテーブル内のすべてのデータが 1 つの統計値に集計されるため、他の関数を使用することは意味がありません。現時点での各レコード)。
混乱していますか?はい私も。戻って、もっと単純なものを見てみましょう。
私たちは何を学びましたか?
SELECT ステートメントは、単純そうに見えても、おそらく SQL ステートメントの中で最も難しい部分です。他のステートメントの機能は、実際にはテーブルへのさまざまな形式の参照です。 SELECT ステートメントはこれらの参照を統合し、論理ルールに従ってソース テーブルをターゲット テーブルにマップします。さらに、このプロセスは可逆的であり、ターゲット テーブル内のデータがどのように取得されたかを明確に知ることができます。
SQL 言語をしっかり学びたい場合は、SELECT ステートメントを使用する前に他のステートメントを理解する必要がありますが、SELECT は文法構造の最初のキーワードですが、最後に習得する必要があります。
複雑な SELECT Yu オペラを学習した後、簡単なものを見てみましょう:
set 演算 (DISTINCT と UNION)
ソート操作 (ORDER BY、OFFSET...FETCH)
集合操作 (集合操作):
集合操作の主な操作は集合に関するもので、実際には一種のテーブル操作を指します。概念的には、これらは簡単に理解できます:
DISTINCT はマッピング後にデータを重複排除します
UNION は 2 つのサブクエリを連結して重複を排除します
UNION ALL は 2 つのサブクエリを連結しますが、重複排除は行いません
EXCE PT の結果を削除します。最初のサブクエリの 2 番目の単語クエリ
INTERSECT 両方のサブクエリで結果を保持し、重複を排除します
並べ替え操作 (並べ替え操作):
並べ替え操作は、論理関係とは何の関係もありません。これは SQL 固有の機能です。ソート操作は SQL ステートメントの最後に実行されるだけでなく、SQL ステートメントの実行中の最後にも実行されます。 ORDER BY と OFFSET...FETCH を使用することは、データが順番に配置されていることを確認する最も効果的な方法です。他のすべての並べ替え方法はある程度ランダムですが、生成される並べ替え結果は再現可能です。
OFFSET…SET は、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 -条項/ )。
仕事で SQL を楽しんでみましょう!
他の言語と同じように、SQL 言語をしっかり学びたい場合は、たくさんの練習が必要です。上記の 10 の簡単な手順は、毎日作成する SQL ステートメントをより深く理解するのに役立ちます。その一方で、よくある間違いから多くの経験を蓄積することができます。次の 2 つの記事では、JAVA や他の開発者が犯す一般的な SQL の間違いをいくつか紹介します:
関連する推奨事項:
SQL でツリー状の階層データを解析するためのクエリの最適化
データ クエリでは、2008 年以降、SQL Server は操作に特別に使用される新しいデータ型階層 ID を提供します...
以上がSQL を完全に解析するには、たった 10 の簡単なステップが必要ですの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。