技术开发 频道

抛开程序员思维定式,简单几步掌握SQL

  【IT168 评论】大多数程序员,面对SQL的第一直觉,往往是按思考命令行或面向对象语言的方式,想着机器是如何一步步执行的,先这样、再那样,咦?参数传进来了吗?满足条件A、B吗?跳进了自己设定的圈中,无法自拔,把简单的问题复杂化了。SQL是少数几个声明性语言之一。它的很多使用方式可能会让很多人不习惯,它与命令行语言、面向对象语言的使用方式有很大不同,本文主要关注select语句的使用。

抛开程序员思维定式,简单几步掌握SQL

  在使用之前,首先要知道一点:SQL是声明性的。你只需要直接声明你想要的结果,例如:

抛开程序员思维定式,简单几步掌握SQL

  我想知道工资大于10万的员工名字,至于这些员工记录是如何获取的,从哪里获取的,你完全不需要关心,这种方式岂不是很美好?

  SQL语法是无序的

  混乱的执行顺序是SQL语法的常态,常常让人摸不着头脑。SQL语句的语法顺序是(正常的书写顺序):

抛开程序员思维定式,简单几步掌握SQL

  简单起见,没有列出所有的SQL子句。这种语法顺序与执行顺序(根据优化器选择)有本质的区别,其执行顺序如下:

抛开程序员思维定式,简单几步掌握SQL

  这里有三件事要注意:

  1、FROM是第一子句,而不是SELECT。执行时的第一件事是将数据从磁盘加载到内存中(某些数据库,数据是从硬盘抽取到数据缓冲区中的),以便对这些数据进行操作。

  2、SELECT在大多数语句之后执行,严格地说,在FROM和GROUP BY之后执行。当你认为你可以在WHERE子句中使用SELECT子句中声明的东西时,是完全错误的。

抛开程序员思维定式,简单几步掌握SQL

  如果,你想重用别名z,你有两个选择。一是重写所有涉及到的表达式。

抛开程序员思维定式,简单几步掌握SQL

  二是求助衍生表,通用数据表达式或视图,以避免别名重用。

  3、UNION的位置一定是在ORDER BY之前,无论是在语法还是执行排序上。许多人认为每个UNION子查询都可以用ORDER BY实现,但根据SQL标准和大多数SQL语言的执行来看,这并不是真的。虽然有些地方允许SQL语言对子查询或派生表排序,但不能保证在UNION操作后仍可以保留这种排序。

  注意,并非所有数据库都以相同的方式实现事务。例如,上述方式的规则2就不能完全适用于MySQL,PostgreSQL和SQLite数据库。

  始终记住SQL子句的语法顺序和执行顺序,以避免犯一些常见的低级错误。如果你可以理解其中的区别,什么事情能做,什么事情不能做就会非常明显。当然,如果语法的设计方式直接可以反映出执行顺序,这种方式对程序员是十分友好的。例如,微软的LINQ。

  SQL中的头等公民是表引用

  由于语法排序和执行顺序之间的差异,大多数初学者可能认为列值是SQL中的头等公民。 其实不然,最重要的是表引用。

  SQL标准定义了FROM子句:

抛开程序员思维定式,简单几步掌握SQL

  FROM子句的“输出”是所有表引用在某一维度上的联合,接下来,我们慢慢消化一下。

抛开程序员思维定式,简单几步掌握SQL

  以上是对a表和b表的联合,如果a有3列,b有5列,那么“输出表”将会有8(3+5)列。其中包含的记录是a x b的结果(笛卡尔积),如果a有3条记录,b有5条记录,则上述组合表引用将产生15条记录。

  这个“输出”被“输送”/“管道化”到GROUP BY子句中(在WHERE子句中过滤之后),在那里它被转换成一个新的“输出”,为以后的操作做准备。如果我们从关系代数/集合理论的角度来看这些东西,一个SQL表是一个或一组元组,每个SQL子句将转换成一个或多个关系,以产生新的关系。

  也就是说,面对SQL语句,应该首先从表的角度思考问题,就可以理解整个SQL语句在执行过程中的变化了。

  SQL的派生表引用功能相当强大

  表引用相当强大。举一个简单的例子是JOIN关键字,它实际上不是SELECT语句的一部分,而是“特殊”表引用的一部分。 如SQL标准(简化)中连接表的定义:

抛开程序员思维定式,简单几步掌握SQL

  我们可以再举个例子:

抛开程序员思维定式,简单几步掌握SQL

  a可能是这样的:

抛开程序员思维定式,简单几步掌握SQL

  将此扩展到上一个表达式中,我们可以得到:

抛开程序员思维定式,简单几步掌握SQL

  虽然不鼓励用逗号分隔两个表的语法出现,但你可以这样做,所得到的组合表将有a1 + a2 + b个维度。

  派生表比连接表更强大,接下来要讲到表连接。

  在以表引用的角度思考SQL的基础上,要了解JOIN是用于构造连接表的关键字,虽然不是SELECT语句的一部分,但一些数据库允许在INSERT,UPDATE,DELETE中使用JOIN。

  应使用JOIN进行表连接,而不是逗号

  之前,我们已经看到这个子句:

抛开程序员思维定式,简单几步掌握SQL

  高级的SQL开发人员可能会告诉你,不鼓励使用逗号分隔的表,应该使用JOIN,因为这有助于提高SQL语句的可读性,从而防止错误。

  一个很常见的错误是在某处忘记JOIN谓词,想一下这个语句:

抛开程序员思维定式,简单几步掌握SQL

  语句太长,可能会在某处忘记JOIN谓词,但其连接表语法的好处是:

  1、可以把连接谓词放在连接表附近,从而防止错误。

  2、更具表达性,可以区分外连接和内连接。

  JOIN操作基本上有五种方式:

抛开程序员思维定式,简单几步掌握SQL

  这些术语通常用在关系代数中。SQL对上述概念使用不同的术语,如果它们存在的话。

  EQUI JOIN

  这是最常见的JOIN操作。 它有两种连接方式:

  · INNER JOIN (或者是 JOIN)

  · OUTER JOIN (包括LEFT, RIGHT, FULL OUTER JOIN)

  他们的区别通过示例解释是:

抛开程序员思维定式,简单几步掌握SQL

  SEMI JOIN

  此关系概念可以在SQL中以两种方式表示:使用IN谓词或使用EXISTS谓词。 “Semi”在拉丁语中是“半”的意思。 此类型的连接仅用于连接表引用的“一半”。 这意味着什么? 再次考虑上面作者和书的示例。 我们不想要作者/书的组合,而只要那些实际上有书的作者信息。我们可以这样写:

抛开程序员思维定式,简单几步掌握SQL

    虽然没有规则说明应该用IN还是EXISTS,但有些东西可以说明:

  1、IN比EXISTS更易读

  2、EXISTS往往比IN更具表达性(即更容易表达非常复杂的SEMI JOIN)

  3、性能上没有明显差异。不过,在某些数据库上可能存在巨大的性能差异。

  因为INNER JOIN只生成那些实际上有书的作者信息,所以许多初学者可能会认为他们可以使用DISTINCT删除重复的图书,他们认为可以这样表达一个SEMI JOIN:

抛开程序员思维定式,简单几步掌握SQL

  这是非常糟糕的做法,有两个原因:

  1、它非常慢,因为数据库必须加载大量的数据到内存,只是为了删除重复的副本。

  2、它不完全正确,即使在这个简单的例子中产生正确的结果。一旦加入更多的表引用,将非常难以正确地从结果中删除重复的副本。

  ANTI JOIN

  这个关系概念恰恰与SEMI JOIN相反。可以通过在IN或EXISTS谓词中添加NOT关键字来简单地生成它。例如,我们将选择没有任何书籍的作者:

抛开程序员思维定式,简单几步掌握SQL

  关于性能,可读性和表达性的规则同样适用。 然而,当使用NOT IN时,有一个关于NULL的小警告,这有点超出本教程的范围。

  CROSS JOIN

  这将产生两个连接表引用的叉积,将第一个表引用的每个记录与第二个表引用的每个记录合并。我们之前已经看到,这可以通过FROM子句中的逗号分隔表引用来实现。在实际应用中,很少用到 CROSS JOIN,如果需要,可以这样表达:

抛开程序员思维定式,简单几步掌握SQL

  简而言之,如果JOIN是乘法,那么DIVISION就是是JOIN的逆。关系分割在SQL中很难表达。因为这是一个适用初学者的教程,所以,这里便不再赘述.

  总之,SQL的重点是表引用。连接表是相当复杂的表引用。但是在关系语言和SQL语言中有一个区别。并非所有关系连接操作都是正确的SQL连接操作。通过一些关于关系理论的实践和认知,将能够选择正确类型的JOIN,并能够将其转换为正确的SQL。

  SQL派生表就像表变量

  之前,我们了解到SQL是一个声明性语言,因此,没有变量(一些SQL衍生语言中可能会有)。但你可以写一些变量,这些变量可以称为派生表。派生表是用括号括起来的子查询。

抛开程序员思维定式,简单几步掌握SQL

  注意,一些SQL示例中需要派生表具有相关的名称(也称为别名)。

抛开程序员思维定式,简单几步掌握SQL

  当你想要规避由SQL子句逻辑排序导致的问题时,派生表是不二选择的。 例如,如果要在SELECT和WHERE子句中重用列表达式,只需写(Oracle):

抛开程序员思维定式,简单几步掌握SQL

  注意,一些数据库和SQL:1990 标准中,派生表被归为通用表语句(common table experssion)。允许用户在一个 SELECT 语句中对派生表多次重用。上面的例子(几乎)等价于下面的语句:

抛开程序员思维定式,简单几步掌握SQL

  显然,你还可以将“a”外部化为独立视图,以便更广泛地重用该派生表。

  SQL GROUP BY是针对表引用的

  让我们重新考虑我们以前的FROM子句:

抛开程序员思维定式,简单几步掌握SQL

  现在,让我们将GROUP BY子句应用到上述组合表中引用

抛开程序员思维定式,简单几步掌握SQL

  上面产生了一个有三个字段的新的表引用。如果应用GROUP BY,将减少所有后续逻辑子句的可用列数,包括SELECT。 这就是为什么select 后面的所有列中没有使用聚合函数的列,而必须出现在 group by 后面的原因了。

  请注意,其他列仍可用作聚合函数的参数:

抛开程序员思维定式,简单几步掌握SQL

  不幸的是,MySQL不遵守这个标准,并会造成混乱。总结来看,GROUP BY可以再次对表引用进行操作,将它们转换为新的形式。

  SQL中的SELECT在关系代数中称为投影

  我个人很喜欢“投影”这个术语,它用于关系代数中。在SQL中, 一旦生成表引用,就可以对其进行过滤,转换,将其投影成另一种形式。SELECT子句就像一个投影机。 表函数使用行值表达式将每个记录从先前构造的表引用转换为最终结果。

  在SELECT子句中,最后可以对列执行操作,从而创建复杂的列表达式作为记录/行的一部分。关于表达式,函数等的性质,有很多特殊的规则。最重要的是,你应该记住这些:

  1、你只能使用那些可以通过表引用得到的字段。

  2、.如果有GROUP BY子句,则只能引用该子句的列或聚合函数。

  3、没有GROUP BY子句时,可以使用窗口函数代替聚合函数。

  4、如果没有GROUP BY子句,则不能将聚合函数与非聚合函数组合。

  5、有关在聚合函数中封装常规函数的一些方法,反之亦然......

  有很多复杂的规则,这里无法全部列举出来。而在不使用GROUP BY子句的SELECT语句的投影中,不能将聚合函数与非聚合函数组合的原因是:

  1、直观地讲,没意义。

  第一直觉就是没意义(尤其是对SQL初学者而言,几乎没有任何价值)。SQL:1999介绍了GROUPING SETS,而SQL:2003引入了空分组集:GROUP BY ()。无论何时存在聚合函数,并且没有显式的GROUP BY子句,都将应用隐式的空GROUPING SET(规则编号2)。虽说这件事让我很困惑,但确实如此。

  SELECT子句可能是SQL中最复杂的子句之一,即使它看起来很简单。 其他语句的作用其实就是对表不同形式的引用 ,而SELECT子句完全转换了这些表引用,对这些表引用进行各类操作,并且过程还可逆。

  为了理解SQL,在尝试解决SELECT之前,先了解一些基本情况是重要的。虽然SELECT是语法排序中的第一个子句,但不急着第一个掌握。

  SQL DISTINCT,UNION,ORDER BY和OFFSET都很简单

  在看完复杂的SELECT语句之后,我们可以再次回到简单的事情:

  集合运算(DISTINCT和UNION)

  排序运算(ORDER BY,OFFSET .. FETCH)

  集合运算就是对“集合”进行操作,这实际上不是...表,好吧,差不多。在概念上,他们很容易理解。

抛开程序员思维定式,简单几步掌握SQL

  所有这些删除重复通常是无意义的。当你想要连接多个子查询时,你应该使用UNION ALL。

  排序不是关系特征。它是一个仅限于SQL的功能。它应用于SQL语句的双向排序和逻辑排序的结尾。使用ORDER BY和OFFSET .. FETCH是保证记录可以通过索引以可靠的方式访问的唯一方法。所有其他排序总是任意和随机的,即使它可能看起来是可再现的。

  OFFSET .. FETCH只有一个语法变体。其他变体包括MySQL和PostgreSQL的LIMIT .. OFFSET或SQL Server和Sybase的TOP...START AT。

  和每种语言一样,SQL也需要很多练习来掌握。以上10个简单的步骤将帮助你更好地了解你每天写的SQL语句。其次,从常见的错误中学习也是一个很好的办法。


0
相关文章