↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

不受支持的版本: 7.1 / 7.0 / 6.5
历史版本PostgreSQL 7.0 已于 2005 年 5 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本手册首页。

69.4. SQL 语言 #

与大多数现代关系语言一样,SQL 基于元组关系演算。 因此,每个能用元组关系演算(或者等价地,关系代数)表述的查询也都能用 SQL 表述。不过,SQL 也有一些超出 关系代数或演算范围的能力。下面列出 SQL 提供的一些 不属于关系代数或演算的附加特性:

  • 用于插入、删除或修改数据的命令。

  • 算术能力:在 SQL 中可以使用算术运算和比较,例如:

    A < B + 3.
    

    注意 + 或其他算术操作符既不出现在关系代数中,也不出现在关系演算中。

  • 赋值和打印命令:可以打印由查询构造出的关系,也可以把计算得到的关系 赋给一个关系名。

  • 聚合函数:可以对关系的列应用诸如平均值、 求和、最大值等操作, 以获得单一的量。

69.4.1. Select #

SQL 中最常用的命令是用于检索数据的 SELECT 语句。其语法为:

SELECT [ALL|DISTINCT]
    { * | expr_1 [AS c_alias_1] [, ... 
     [, expr_k [AS c_alias_k]]]}
    FROM table_name_1 [t_alias_1] 
     [, ... [, table_name_n [t_alias_n]]]
    [WHERE condition]
    [GROUP BY name_of_attr_i 
     [,... [, name_of_attr_j]] [HAVING condition]]
    [{UNION [ALL] | INTERSECT | EXCEPT} SELECT ...]
    [ORDER BY name_of_attr_i [ASC|DESC] 
     [, ... [, name_of_attr_j [ASC|DESC]]]];
     

下面我们将用各种示例来说明 SELECT 语句的复杂语法。示例所用的表定义于 供应商与零件数据库。

69.4.1.1. 简单 Select

下面是一些使用 SELECT 语句的简单示例:

例 69.4. 带限定条件的简单查询

要检索表 PART 中属性 PRICE 大于 10 的所有元组,我们写出如下查询:

SELECT * FROM PART
    WHERE PRICE > 10;
        

并得到表:

 PNO |  PNAME  |  PRICE
-----+---------+--------
  3  |  Bolt   |   15
  4  |  Cam    |   25
        

在 SELECT 语句中使用 "*" 将交付该表的所有属性。如果只想检索表 PART 的属性 PNAME 和 PRICE,我们使用如下语句:

SELECT PNAME, PRICE 
    FROM PART
    WHERE PRICE > 10;
        

此时结果为:

                      PNAME  |  PRICE
                     --------+--------
                      Bolt   |   15
                      Cam    |   25
        

注意,SQL 的 SELECT 对应于关系代数中的"投影"而不是"选择"(更多细节见 关系代数)。

WHERE 子句中的限定条件也可以用关键字 OR、AND 和 NOT 进行逻辑连接:

SELECT PNAME, PRICE 
    FROM PART
    WHERE PNAME = 'Bolt' AND
         (PRICE = 0 OR PRICE < 15);
        

将得到结果:

 PNAME  |  PRICE
--------+--------
 Bolt   |   15
        

目标列表和 WHERE 子句中可以使用算术运算。例如,如果我们想知道取一个零件的两件要花多少钱,可以用如下查询:

SELECT PNAME, PRICE * 2 AS DOUBLE
    FROM PART
    WHERE PRICE * 2 < 50;
        

我们得到:

 PNAME  |  DOUBLE
--------+---------
 Screw  |    20
 Nut    |    16
 Bolt   |    30
        

注意,关键字 AS 之后的单词 DOUBLE 是第二列的新标题。此技术可用于目标列表的每个元素,为结果列指派新标题。这个新标题通常称为别名。别名不能在查询的其余部分中使用。


69.4.1.2. 连接

下面的示例演示了SQL中如何实现连接。

要通过公共属性连接 SUPPLIER、PART 和 SELLS 三张表,我们表述如下语句:

SELECT S.SNAME, P.PNAME
    FROM SUPPLIER S, PART P, SELLS SE
    WHERE S.SNO = SE.SNO AND
          P.PNO = SE.PNO;

并得到以下表作为结果:

 SNAME | PNAME
-------+-------
 Smith | Screw
 Smith | Nut
 Jones | Cam
 Adams | Screw
 Adams | Bolt
 Blake | Nut
 Blake | Bolt
 Blake | Cam
      

在 FROM 子句中,我们为每个关系引入了一个别名,因为这些关系之间存在 同名属性(SNO 和 PNO)。现在只需在属性名前加上别名和一个点号,就可以 区分这些同名属性。连接的计算方式与 一个内连接中所示的相同。 首先导出笛卡尔积 SUPPLIER × PART × SELLS 然后只选出满足 WHERE 子句所给条件的元组(即同名属性必须相等)。 最后把除 S.SNAME 和 P.PNAME 之外的所有列都投影出去。

69.4.1.3. 聚合函数

SQL 提供聚合运算符(例如 AVG、COUNT、SUM、MIN、MAX),它们接受属性名作为参数。聚合运算符的值是在整张表的指定属性(列)的所有值上计算的。如果查询中指定了组,则只在该组的值上计算(见下一节)。

例 69.5. 聚合

如果我们想知道表 PART 中所有零件的平均价格,使用以下查询:

SELECT AVG(PRICE) AS AVG_PRICE
    FROM PART;
        

结果是:

 AVG_PRICE
-----------
   14.5
        

如果我们想知道表 PART 中存储了多少个零件,我们使用如下语句:

SELECT COUNT(PNO)
    FROM PART;
        

并得到:

 COUNT
-------
   4
        


69.4.1.4. 按组聚合

SQL 允许把表的元组划分成组。然后就可以把上面描述的聚合运算符应用到这些组上(即聚合运算符的值不再是对指定列的所有值计算,而是对一个组的所有值计算。这样,聚合运算符会对每个组分别求值。)

把元组划分成组的工作通过关键字 GROUP BY 后跟定义 组的属性列表来完成。如果有 GROUP BY A1, [tdot ], Ak, 我们就把关系划分为若干组,使得两个元组处于同一组当且仅当它们在所有 属性 A1, [tdot ], Ak 上 都一致。

例 69.6. 聚合

如果我们想知道每个供应商销售多少个零件,可以表述如下查询:

SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
    FROM SUPPLIER S, SELLS SE
    WHERE S.SNO = SE.SNO
    GROUP BY S.SNO, S.SNAME;
        

并得到:

 SNO | SNAME | COUNT
-----+-------+-------
  1  | Smith |   2
  2  | Jones |   1
  3  | Adams |   2
  4  | Blake |   3
        

现在来看看这里发生了什么。首先导出表 SUPPLIER 和 SELLS 的连接:

 S.SNO | S.SNAME | SE.PNO
-------+---------+--------
   1   |  Smith  |   1
   1   |  Smith  |   2
   2   |  Jones  |   4
   3   |  Adams  |   1
   3   |  Adams  |   3
   4   |  Blake  |   2
   4   |  Blake  |   3
   4   |  Blake  |   4
        

接下来,把在 S.SNO 和 S.SNAME 两个属性上都一致的元组放在一起, 将元组划分为组:

 S.SNO | S.SNAME | SE.PNO
-------+---------+--------
   1   |  Smith  |   1
                 |   2
--------------------------
   2   |  Jones  |   4
--------------------------
   3   |  Adams  |   1
                 |   3
--------------------------
   4   |  Blake  |   2
                 |   3
                 |   4
        

在本例中我们得到了四个组,现在可以对每个组应用聚合运算符 COUNT,从而得到上面给出的查询的最终结果。


注意,要让使用 GROUP BY 和聚合运算符的查询结果有意义,分组所用的属性也必须出现在目标列表中。其他未出现在 GROUP BY 子句中的属性只能通过聚合函数来选择。另一方面,不能对出现在 GROUP BY 子句中的属性使用聚合函数。

69.4.1.5. Having

HAVING 子句的作用与 WHERE 子句很像,用于只考虑满足 HAVING 子句中限定条件的那些组。HAVING 子句中允许的表达式必须涉及聚合函数。每个只使用普通属性的表达式都属于 WHERE 子句。另一方面,每个涉及聚合函数的表达式都必须放到 HAVING 子句中。

例 69.7. Having

如果我们只想要销售多于一个零件的供应商,我们使用如下查询:

SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
    FROM SUPPLIER S, SELLS SE
    WHERE S.SNO = SE.SNO
    GROUP BY S.SNO, S.SNAME
    HAVING COUNT(SE.PNO) > 1;
        

并得到:

 SNO | SNAME | COUNT
-----+-------+-------
  1  | Smith |   2
  3  | Adams |   2
  4  | Blake |   3
        


69.4.1.6. 子查询

在 WHERE 和 HAVING 子句中,凡是期望一个值的地方都允许使用子查询(子 SELECT)。此时该值必须先通过求值子查询得出。子查询的使用扩展了 SQL 的表达能力。

例 69.8. 子选择

如果我们想知道所有比名为 'Screw' 的零件价格更高的零件,我们使用如下查询:

SELECT * 
    FROM PART 
    WHERE PRICE > (SELECT PRICE FROM PART
                   WHERE PNAME='Screw');
        

结果是:

 PNO |  PNAME  |  PRICE
-----+---------+--------
  3  |  Bolt   |   15
  4  |  Cam    |   25
        

观察上面的查询可以看到关键字 SELECT 出现了两次。第一次在查询的开头——我们称之为外层 SELECT——另一次在 WHERE 子句中,它开始一个嵌套查询——我们称之为内层 SELECT。对外层 SELECT 的每个元组,都必须求值内层 SELECT。每次求值后我们就知道了名为 'Screw' 的元组的价格,从而可以检查当前元组的价格是否更大。

如果我们想知道没有销售任何零件的供应商(例如,以便能从数据库中删除 这些供应商),使用:

SELECT * 
    FROM SUPPLIER S
    WHERE NOT EXISTS
        (SELECT * FROM SELLS SE
         WHERE SE.SNO = S.SNO);
        

在本例中结果将为空,因为每个供应商都至少销售一个零件。注意我们在内层 SELECT 的 WHERE 子句中使用了来自外层 SELECT 的 S.SNO。如上所述,子查询对外层查询的每个元组求值,即 S.SNO 的值总是取自外层 SELECT 的当前元组。


69.4.1.7. Union, Intersect, Except

这些运算计算由两个子查询导出的元组的并集、交集和集合论差集。

例 69.9. Union, Intersect, Except

下面的查询是 UNION 的例子:

SELECT S.SNO, S.SNAME, S.CITY
    FROM SUPPLIER S
    WHERE S.SNAME = 'Jones'
    UNION
    SELECT S.SNO, S.SNAME, S.CITY
    FROM SUPPLIER S
    WHERE S.SNAME = 'Adams';

给出结果:

 SNO | SNAME |  CITY
-----+-------+--------
  2  | Jones | Paris
  3  | Adams | Vienna
        

下面是 INTERSECT 的一个例子:

SELECT S.SNO, S.SNAME, S.CITY
    FROM SUPPLIER S
    WHERE S.SNO > 1
    INTERSECT
    SELECT S.SNO, S.SNAME, S.CITY
    FROM SUPPLIER S
    WHERE S.SNO > 2;
        

给出结果:

 SNO | SNAME |  CITY
-----+-------+--------
  2  | Jones | Paris
        

查询两部分都返回的唯一元组是 $SNO=2$ 的那一个。

最后是 EXCEPT 的例子:

SELECT S.SNO, S.SNAME, S.CITY
    FROM SUPPLIER S
    WHERE S.SNO > 1
    EXCEPT
    SELECT S.SNO, S.SNAME, S.CITY
    FROM SUPPLIER S
    WHERE S.SNO > 3;
        

给出结果:

 SNO | SNAME |  CITY
-----+-------+--------
  2  | Jones | Paris
  3  | Adams | Vienna
        


69.4.2. 数据定义 #

SQL 语言中包含一组用于数据定义的命令。

69.4.2.1. 创建表 #

数据定义最基本的命令是创建新关系(新表)的命令。 CREATE TABLE 命令的语法为:

CREATE TABLE table_name
    (name_of_attr_1 type_of_attr_1
     [, name_of_attr_2 type_of_attr_2 
     [, ...]]);
      

例 69.10. 创建表

要创建供应商与零件数据库中定义的表, 使用以下 SQL 语句:

CREATE TABLE SUPPLIER
    (SNO   INTEGER,
     SNAME VARCHAR(20),
     CITY  VARCHAR(20));
     
CREATE TABLE PART
    (PNO   INTEGER,
     PNAME VARCHAR(20),
     PRICE DECIMAL(4 , 2));
     
CREATE TABLE SELLS
    (SNO INTEGER,
     PNO INTEGER);
        


69.4.2.2. SQL 中的数据类型

下面是 SQL 支持的一些数据类型的列表:

  • INTEGER:有符号全字二进制整数(31 位精度)。

  • SMALLINT:有符号半字二进制整数(15 位精度)。

  • DECIMAL (p[,q]): 有符号压缩十进制数,精度为 p 位数字,并假定其中 q 位在小数点右边。 (15 ≥ p ≥ q ≥ 0). 如果省略 q, 则假定为 0。

  • FLOAT:有符号双字浮点数。

  • CHAR(n): 长度为 n 的定长 字符串。

  • VARCHAR(n): 最大长度为 n 的 变长字符串。

69.4.2.3. 创建索引

索引用来加快对关系的访问。如果关系 R 在属性 A 上有一个索引,那么检索所有满足 t(A) = a 的元组 t 时,所需时间大致与这类元组 t 的数量成正比,而不再与 R 的大小成正比。

要在 SQL 中创建索引,使用 CREATE INDEX 命令。其语法为:

CREATE INDEX index_name 
    ON table_name ( name_of_attribute );
      

例 69.11. 创建索引

要在关系 SUPPLIER 的属性 SNAME 上创建名为 I 的索引,使用以下语句:

CREATE INDEX I ON SUPPLIER (SNAME);
      

创建的索引会被自动维护,即每当有新元组插入关系 SUPPLIER 时,索引 I 都会随之调整。注意,存在索引时用户能察觉到的唯一变化是速度的提高。


69.4.2.4. 创建视图

视图可以被看作一张虚拟表,即一张在数据库中 并不物理存在、但在用户看来好像存在的表。相比之下, 当我们谈论基表时,表的每一行在物理存储中的 某处确实有一个物理存储的对应物。

视图没有自己的、物理上分离且可区分的存储数据。相反,系统把视图的定义(即如何访问物理存储的基表以物化该视图的规则)存储在系统目录的某处(见 系统目录)。关于实现视图的不同技术的讨论,参见 SIM98。

在 SQL 中使用 CREATE VIEW 命令定义视图。语法为:

CREATE VIEW view_name
    AS select_stmt

其中 select_stmt 是一个 如Select中所定义的有效的 select 语句。注意,创建视图时并不执行 select_stmt,它只是被存储在 系统目录中,每当对视图进行查询时才被执行。

给定下面的视图定义(我们再次使用 供应商与零件数据库中的表):

CREATE VIEW London_Suppliers
    AS SELECT S.SNAME, P.PNAME
        FROM SUPPLIER S, PART P, SELLS SE
        WHERE S.SNO = SE.SNO AND
              P.PNO = SE.PNO AND
              S.CITY = 'London';
      

现在我们就可以像使用另一张基表一样使用这个 虚拟关系 London_Suppliers:

SELECT * FROM London_Suppliers
    WHERE P.PNAME = 'Screw';
      

它将返回以下表:

 SNAME | PNAME
-------+-------
 Smith | Screw                 
      

为了计算这个结果,数据库系统必须先隐蔽地访问 基表 SUPPLIER、SELLS 和 PART。它通过对这些基表执行视图定义中给出的 查询来完成这一步。之后,再应用(针对视图的查询中给出的)附加限定条件, 即可得到结果表。

69.4.2.5. Drop Table、Drop Index、Drop View

要销毁一个表(包括该表中存储的所有元组),使用 DROP TABLE 命令:

DROP TABLE table_name;
       

要销毁 SUPPLIER 表,使用以下语句:

DROP TABLE SUPPLIER;
      

要销毁一个索引,使用 DROP INDEX 命令:

DROP INDEX index_name;
      

最后,要销毁一个给定的视图,使用命令 DROP VIEW:

DROP VIEW view_name;
      

69.4.3. 数据操纵

69.4.3.1. Insert Into

一旦创建了表(见 创建表),就可以用 INSERT INTO 命令向其中填入元组。语法为:

INSERT INTO table_name (name_of_attr_1 
    [, name_of_attr_2 [,...]])
    VALUES (val_attr_1 [, val_attr_2 [, ...]]);
      

要向关系 SUPPLIER(来自 供应商与零件数据库)插入第一个元组, 使用以下语句:

INSERT INTO SUPPLIER (SNO, SNAME, CITY)
    VALUES (1, 'Smith', 'London');
      

要向关系 SELLS 插入第一个元组,使用:

INSERT INTO SELLS (SNO, PNO)
    VALUES (1, 1);
      

69.4.3.2. Update

要更改关系中元组的一个或多个属性值,使用 UPDATE 命令。其语法为:

UPDATE table_name
    SET name_of_attr_1 = value_1 
        [, ... [, name_of_attr_k = value_k]]
    WHERE condition;
      

要修改关系 PART 中零件'Screw'的属性 PRICE 的值,使用:

UPDATE PART
    SET PRICE = 15
    WHERE PNAME = 'Screw';
      

名为'Screw'的元组的属性 PRICE 的新值现在是 15。

69.4.3.3. Delete

要从特定的表中删除元组,使用 DELETE FROM 命令。语法为:

DELETE FROM table_name
    WHERE condition;
      

要删除表 SUPPLIER 中名为'Smith'的供应商,使用以下语句:

DELETE FROM SUPPLIER
    WHERE SNAME = 'Smith';
      

69.4.4. 系统目录 #

在每个 SQL 数据库系统中,都使用 系统目录来记录数据库中定义了哪些表、视图、 索引等。这些系统目录可以像普通关系一样被查询。例如,有一个用于视图 定义的目录,该目录存储视图定义中的查询。每当对视图进行查询时,系统 首先从目录中取出视图定义查询并物化该视图, 然后再继续处理用户的查询(更详细的描述见 Simkovics, 1998 )。关于系统目录的更多信息,参见 Date, 1994 。

69.4.5. 嵌入式 SQL

本节将概述如何把 SQL 嵌入到宿主语言 (例如 C)中。我们想从宿主语言中使用 SQL 有两个主要原因:

  • 有些查询无法用纯 SQL 表述(即递归查询)。要能够 执行这类查询,我们需要一种表达能力比 SQL 更强 的宿主语言。

  • 我们只是想从用宿主语言编写的应用程序访问数据库(例如,一个带图形 用户界面的订票系统用 C 编写,而关于还剩哪些票的信息存储在可以用 嵌入式 SQL 访问的数据库中)。

在宿主语言中使用嵌入式 SQL 的程序由宿主语言的 语句和嵌入式 SQL (ESQL)语句组成。每条 ESQL 语句都以关键字 EXEC SQL 开头。 ESQL 语句由预编译器转换成 宿主语言的语句(预编译器通常会插入对库例程的调用,由这些例程执行各种 SQL 命令)。

纵观 Select 中的示例可以意识到,查询的结果常常是一个元组的集合。而大多数宿主语言并非为操作集合而设计,因此我们需要一种机制来访问 SELECT 语句返回的元组集合中的每一个元组。这一机制可以通过声明一个游标来提供。之后我们就可以用 FETCH 命令检索一个元组并把游标移到下一个元组。

关于嵌入式 SQL 的详细讨论,参见 Date and Darwen, 1997 、 Date, 1994 或 Ullman, 1988 。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。