많은 프로그래머는 SQL을 재앙으로 여깁니다. SQL은 명령줄 언어, 객체지향 프로그래밍 언어, 심지어 우리에게 익숙한 기능적 언어와도 완전히 다르게 작동하는 몇 안 되는 선언적 언어 중 하나입니다(어떤 사람들은 SQL 언어도 기능적 언어라고 생각하지만). ).
우리는 매일 SQL을 작성하고 이를 오픈소스 소프트웨어인 jOOQ에 적용합니다. 그래서 아직도 SQL에 대해 고민하고 있는 친구들에게 SQL의 아름다움을 소개하고 싶어서 이 글은 다음 독자들을 위해 특별히 작성되었습니다.
1. 직장에서 SQL을 사용할 예정이지만 SQL을 완전히 이해하지 못하는 사람들.
2. SQL을 능숙하게 사용하지만 구문과 논리를 이해하지 못하는 사람들.
3. 다른 사람에게 SQL을 가르치고 싶은 사람.
이 글은 SELECT 문장 패턴에 초점을 맞추고 있으며, 기타 DML(Data Manipulation Language) 명령은 다른 글에서 소개할 예정입니다.
우선 "선언"이라는 개념을 명심해야 합니다. SQL 언어는 컴퓨터에게 결과를 얻는 방법을 알려주는 대신 원시 데이터에서 얻고자 하는 결과의 종류를 컴퓨터에 선언하는 예입니다. 정말 좋지 않나요?
(역자 주: 간단히 말해서 SQL 언어는 결과 집합의 속성을 선언합니다. 컴퓨터에 전통적인 사용법을 지시하는 것이 아니라 SQL에서 선언한 내용을 기반으로 컴퓨터가 데이터베이스에서 선언에 맞는 데이터를 선택합니다. )
SELECT first_name, last_name FROM employees WHERE salary > 100000
위의 예는 이해하기 쉽습니다. 우리는 이러한 직원 기록이 어디서 왔는지 상관하지 않습니다. 우리에게 필요한 것은 급여가 높은 사람들의 데이터입니다. .
우리는 이것을 어디서 배웠나요?
SQL 언어가 이렇게 단순하다면, 사람들이 “SQL을 듣고 얼굴이 창백해지는” 이유는 무엇입니까? 그 주된 이유는 우리가 무의식적으로 명령형 프로그래밍 사고방식에 따라 문제를 생각하기 때문입니다. 이는 다음과 같습니다: "컴퓨터, 이 단계를 먼저 수행한 다음 저 단계를 수행하고 그 전에 조건 A와 조건 B가 충족되는지 확인하십시오." 예를 들어 변수를 사용하여 매개변수를 전달하는 것, 루프 문을 사용하는 것, 반복, 함수 호출 등은 모두 이 명령형 프로그래밍의 사고 습관입니다.
SQL 문에는 대부분의 사람들을 혼란스럽게 하는 특징이 있습니다. 즉, SQL 문의 실행 순서가 해당 문의 문법 순서와 일치하지 않습니다. SQL 문의 구문 순서는 다음과 같습니다.
SELECT[DISTINCT]
FROM
WHERE
GROUP BY
HAVING
UNION
주문은
이해의 편의를 위해 위에 SQL 구문 구조가 모두 나열되어 있는 것은 아니지만, SQL 문의 구문 순서가 실행 순서와 완전히 다르다는 것을 보여주는 것으로 충분합니다. :
FROM
WHERE
GROUP BY
HAVING
SELECT
DISTINCT
UNION
ORDER BY
실행에 대하여 SQL 문 순서, 주목할 만한 세 가지 사항이 있습니다.
1. SQL 문 실행의 첫 번째 단계는 SELECT가 아니라 FROM입니다. 데이터베이스가 SQL 문을 실행하는 첫 번째 단계는 작업이 가능하도록 하드 디스크의 데이터를 데이터 버퍼로 로드하는 것입니다. (번역자 주: 원문은 "이러한 데이터에 대해 작업을 수행하기 위해 가장 먼저 일어나는 일은 디스크에서 메모리로 데이터를 로드하는 것입니다."이지만, 그렇지 않습니다. Oracle 등 일반적으로 사용되는 데이터베이스를 예로 들면 다음과 같습니다. , 데이터는 데이터 버퍼에서 추출됩니다. )
2. SELECT는 대부분의 명령문이 실행된 후에 실행됩니다. 엄밀히 말하면 FROM 및 GROUP BY 이후에 실행됩니다. 이를 이해하는 것이 매우 중요합니다. 따라서 SELECT에서 별칭이 지정된 WHERE의 필드를 조건으로 사용할 수 없습니다.
SELECT A.x + A.y AS z FROM A WHERE z = 10 -- z 在此处不可用,因为SELECT是最后执行的语句!
별칭 z를 재사용하려면 두 가지 옵션이 있습니다. 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는 위의 두 번째 항목에서 언급한 대로 수행되지 않습니다.
우리는 무엇을 배웠나요?
既然并不是所有的数据库都按照上述方式执行 SQL 预计,那我们的收获是什么?我们的收获是永远要记得: SQL 语句的语法顺序和其执行顺序并不一致,这样我们就能避免一般性的错误。如果你能记住 SQL 语句语法顺序和执行顺序的差异,你就能很容易的理解一些很常见的 SQL 问题。
当然,如果一种语言被设计成语法顺序直接反应其语句的执行顺序,那么这种语言对程序员是十分友好的,这种编程语言层面的设计理念已经被微软应用到了 LINQ 语言中。
由于 SQL 语句语法顺序和执行顺序的不同,很多同学会认为SELECT 中的字段信息是 SQL 语句的核心。其实真正的核心在于对表的引用。
根据 SQL 标准,FROM 语句被定义为:
44dc542b5d74380f2cd0250ba54efa49 ::= FROM de33f891b201ddbdecd12e0f3bc36d8b [ { ccb011809fb18504b450f66338ebdd64 de33f891b201ddbdecd12e0f3bc36d8b }... ]
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 문은 다음과 같이 처리합니다. 기본적으로 전체 테이블을 그룹화합니다. 집계 함수가 특정 필드에 대해 집계 통계를 수행하면 참조된 테이블의 각 레코드는 의미를 잃습니다. 모든 데이터는 하나의 통계 값으로 집계됩니다. 다른 함수를 사용하는 것은 의미가 없습니다. 현재 각 레코드).
혼란스러우신가요? 응, 나도 마찬가지야. 다시 돌아가서 더 간단한 것을 살펴보겠습니다.
우리는 무엇을 배웠나요?
SELECT 문은 SQL 문에서 단순해 보이지만 아마도 가장 어려운 부분일 것입니다. 다른 명령문의 기능은 실제로 테이블에 대한 다른 형태의 참조입니다. SELECT 문은 이러한 참조를 통합하고 논리적 규칙을 통해 원본 테이블을 대상 테이블에 매핑합니다. 또한 이 프로세스는 되돌릴 수 있으며 대상 테이블의 데이터가 어떻게 오는지 명확하게 알 수 있습니다.
SQL 언어를 잘 배우고 싶다면 SELECT 문을 사용하기 전에 다른 문을 이해해야 합니다. SELECT는 문법 구조의 첫 번째 키워드이지만 우리가 익히는 마지막 키워드여야 합니다.
복잡한 SELECT Yu 오페라를 배운 후 간단한 것을 살펴보겠습니다.
set 연산(DISTINCT 및 UNION)
정렬 작업(ORDER BY, OFFSET...FETCH)
세트 작업(세트 작업):
세트 작업의 주요 작업은 세트에 있으며 실제로는 일종의 테이블 작업을 의미합니다. 개념적으로는 이해하기 쉽습니다.
DISTINCT는 매핑 후 데이터 중복을 제거합니다.
UNION은 두 개의 하위 쿼리를 연결하고 중복을 제거합니다.
UNION ALL은 두 개의 하위 쿼리를 연결하지만 중복 제거는 없습니다
EXCEPT 결과를 제거하십시오. 첫 번째 하위 쿼리의 두 번째 단어 쿼리
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 문을 더 잘 이해하는 데 도움이 될 수 있습니다. 반면에 흔한 실수로 인해 많은 경험이 쌓일 수도 있습니다. 다음 두 문서에서는 JAVA 및 다른 개발자가 저지르는 몇 가지 일반적인 SQL 실수를 소개합니다.
관련 권장 사항:
SQL에서 트리 모양의 계층적 데이터를 구문 분석하기 위한 쿼리 최적화
데이터 쿼리에서 2008년부터 SQL Server는 작업에 특별히 사용되는 새로운 데이터 유형 Hierarchyid를 제공합니다...
위 내용은 SQL을 완벽하게 구문 분석하려면 간단한 10단계만 필요합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!