《Sams Teach Yourself SQL in 10 Minutes (4th Edition)》 —— SQL 入门书

闲来无事,翻一翻,确实作为入门书籍来说还是不错的。

SQL 是使用最广泛的数据库语言。

数据库设计、规范化到关系数据库理论以及管理问题等,一大堆,甚是繁琐,但这些往往不是我们需要的。

从最简单的数据检索(select)入手,逐步过渡一些较为复杂的内容,例如:联结、子查询、存储过程、游标、触发器以及表约束等,循序渐进。

第1课 了解SQL

数据库基础、数据库设计、规范化、关系数据库理论

数据库 :一个以某种有组织的方式存储的数据集合(文件柜、database)。

数据库软件 :数据库管理系统(DBMS),数据库是通过 DBMS 创建和操纵的容器。

:某种特定类型数据的结构化清单(table)。

  • table 表名唯一。
  • 存储在表中的数据是同一种类型的数据或清单。
  • 模式 :关于数据库和表的布局及特性的信息(schema)。

    :(column)字段

  • 表由列组成。
  • 数据类型(date type) —— 数值、字符、文本、日期等
  • :(row) 也称为数据库记录 (record)。

    主键 :唯一标识 (primary key),主键不允许为空值 NULL

    应该总是定义主键

    SQL :Structured Query Language 结构化查询语言

    标准SQL :ANSI SQL

    动手实践 —— The Most Important!!!

    第2课 检索数据

    使用 select 语句从表中检索一个或多个数据列。

    关键字 (keyword):作为 SQL 组成部分的保留字,英语单词。

  • 检索单个列
  • select prod_name from Products;
    select * from table_name;
    

    未排序数据

    过滤 → 子集

    多条 SQL 语句必须以分号(;)分隔

    SQL 语句不区分大小写

  • 检索多个列
  • 多个列,用逗号(;)分隔

    select prod_id,
           prod_name,
           prod_price
    from Products;
    

    数据格式化

  • 检索所有列
  • 使用星号(*)通配符可以检索所有列

    select * from Products;
    

    通配符 *

    检索不需要的列通常会降低检索和应用程序的性能,因此需要确保真的需要所有的列,才使用通配符进行检索。

    检索未知列

  • 检索不同的值 distinct
  • distinct 去重 具有唯一性

    select distinct vend_id from Products;
    

    作用于所有的列 只返回不同的值

    select distinct a,b from table_name;
    

    不能部分使用 distinct

  • 限制结果 limit
  • 只返回一定数量的行

    1.SQL Server & Access  -- TOP
      select top 5 prod_name from table_name;
    2.DB2 -- FETCH FIRST 5 ROWS ONLY
      select prod_name from table_name fetch first 5 rows only;
    3.Oracle -- 基于rownum(行计数器)计算行
      select prod_name from table_name where rownum <= 5;
    4.Mysql & MaraiDB & PostgreSQL & SQLite -- limit
      select * from table_name limit 5;
    
    select * from table_name limit 5 offset 5; -- MySQL 等 DBMS 返回从第5行开始的5行数据
    

    从第5行开始后面的3行数据

    select * from table_name limit 3 offset 4; --  MySQL返回从第5行起的3行数据
    select * from table_name limit 3,4; -- MySQL返回从第4行开始的4行数据
    

    limit m,n

    ​ 其中,m是指记录开始的 index,从0开始,表示第一条记录

    ​ n是指从第m+1条开始,取n条数据

    ​ 初始记录行的偏移量是0,不是1

    ​ limit 95,-1 96-last所有 —— 测试过,不行!

    ​ limit 5 等同于 limit 0,5

    希望包括不进行处理和执行的文本,例如描述性的注释。

    井号#、--、/* */ 等3种方式。

    第3课 排序检索数据

    使用 select 语句的 order by 子句,根据需要排序检索出的数据。

    如果不排序,数据一般将以它在底层表中出现的顺序显示。

    子句(clause):一个子句通常由一个关键字加上所提供的数据组成。

    select *
    from table_name
    order by prod_name;
    

    order by 子句应该放在最后面。

    使用非检索列进行排序数据也是完全合法的。

    按多个列进行排序时,用逗号隔开即可。

    select *
    from table_name
    order by prod_name, prod_id;
    

    按列位置排序

    select prod_id, prod_name, prod_price
    from Products
    order by 2,3; -- 先按第2列进行排序,后按第3列进行排序
    

    不用重新输入列名,却可能导致不明确。

    降序desc

    -- desc descending 缩写

    -- asc ascending 缩写

    select *
    from table_name
    order by prod_name desc; -- asc 升序
    
    select *
    from table_name
    order by prod_name desc, prod_id;
    

    desc 关键字只应用到直接位于其前面的列名。

    因此如果想对多个列都进行降序,必须针对每一列加上 desc 关键字。

    a 和 A 的排序方式则取决于数据库的设置方式。

    第4课 过滤数据

    使用 select 语句的 where 子句指定搜索条件。

    一般只会根据特定操作或者特定需求提取表数据的子集。

    需要指定搜索条件(search criteria)、过滤条件(filter condition)。

    select prod_name, prod_price
    from Products
    where prod_price = 3.49;
    

    同时使用 order bywhere 子句时,应该让 order by 位于 where 之后。

  • where 子句操作符
  • 通过 and 子句或者 or 子句进行组合使用。

    操作符(operator):用来联结或改变 where 子句中的关键字,也称为逻辑操作符(logical operator)。

    select * from Products
    where vend_id = 'DDDD' and prod_price <= 4; -- and 同时满足
    

    多个条件之间都要使用 and 关键字。

    order by 子句放在 where 子句之后。

    select * from Products
    where vend_id = 'DDDD' or prod_price <= 4; -- or 任一满足
    

    DBMS 检索匹配任一条件的行,许多 DBMS 在 or where 子句的第一个条件满足情况下,就不再计算第二个条件。

    and 和 or 两者可以结合以进行复杂、高级的过滤。

    注意优先级。

    select * from Products
    where (vend_id = 'DDDD' or vend_id = 'FFFF')
          and prod_price = 10;
    

    不要过分依赖系统默认的计算顺序,多使用圆括号。

  • in 操作符
  • in 操作符用来指定条件范围,范围内的每个条件都可以匹配。

    select * from Products
    where vend_id in ('DDD', 'FFFF')
    order by prod_name;
    

    类似于 or 的功能。

    in 操作符更加清晰、直观。

    in 性能高于 or 操作符。

    in 可以包含其他 select 语句,能够更加动态地建立 where 子句。

  • not 操作符
  • 否定其后所跟的任何条件。

    select * from Products
    where not vend_id = 'DDDD'; -- 也可以使用 <> 操作符完成
    

    在复杂的语句中,使用 not 才有优势,并且与 in 操作符联合使用的时候,更显优势。

    MariaDB 支持使用 not 否定 in、between 和 exists 子句。

    总结:and、or、not、in、求值顺序。

    第6课 用通配符进行过滤

    使用通配符 * 、like 操作符进行通配搜索,以便对数据进行复杂过滤。

  • like 操作符
  • 利用通配符,可以创建模糊搜索模式。

    通配符(wildcard):用来匹配值的一部分特殊字符。

    搜索模式(search pattern):由字面值 + 通配符或两者组合构成的搜索条件。

    通配符搜索只能用于字符串,非文本数据类型字段无法使用通配符搜索。

  • 百分号(%)通配符
  • 表示任何字符出现任意次数。

    select prod_id, prod_name
    from Products
    where prod_name like 'Fish%'; -- 以 Fish 开头的产品
    

    Access 通配符:使用 * 而不是 %

    不同的 DBMS 区分大小写,具体看配置。

    select prod_id, prod_name
    from Products
    where prod_name like '%bean%'; -- 字符两端
    

    字符中间也可以。

    在匹配邮件地址的时候,很有用。例如:where email like 'b%@qq.com'

    % 还能匹配 0 个字符

    % 代表搜索模式中给定位置的 0 个、1 个或多个字符。

    注意:空格的影响,是否匹配不到数据?

    注意:NULL 无法匹配

  • 下划线(_)通配符
  • 只匹配单个字符,而不是多个字符。

    DB2 不支持通配符 _

    Access 需要使用?而不是 _

    select prod_id, prod_name
    from Products
    where prod_name like '_inch'; -- 单个字符 注意空格
    
  • 方括号([ ])通配符
  • 指定字符集,必须匹配指定位置(通配符的位置)的一个字符。

    Access 和 SQL Server 支持集合

    select cust_contact
    from Customers
    where cust_contact like '[JM]%'
    order by cust_contact;  -- 以J或M开头的名字
    
  • 前缀字符 ^ 表否定
  • select cust_contact
    from Customers
    where cust_contact like '[^JM]%'
    order by cust_contact;  -- 不以J或M开头的名字
    

    Access 中使用 '[!JM]' 进行检索

    使用 not 操作符同样可以得到结果。not like

    通配符的搜索将耗费更多的时间

  • 不要过度的使用通配符;
  • 尽量不要用在搜索模式的开始处,因为无法用到索引;
  • 注意通配符的位置;
  • 第7课 创建计算字段

    关联、and、upper、sum、avg 等计算字段。

    从数据库中检索出转换、计算或格式化过的数据。

    字段(field):意思同列(column)

    在数据库服务器上完成转换和格式化比在客户端中完成快得多。

    拼接(concatenate):将值联结在一起。

    加号(+)或者 两个竖杆(||)表示。

    Mysql 和 MariaDB 使用特殊的函数。

  • Access 和 SQL Server 使用 + 号。
  • DB2、Oracle、PostgreSQL 使用 || 。
  • MySQL、MariaDB 使用 concat 函数。
  • select vend_name + '(' + vend_country + ')'
    from Vendors
    order by vend_name; -- 字符拼接 使用||同理
    
    -- MySQL --
    select concat(vend_name, '(', vend_country, ')')
    from Vendors
    order by vend_name;
    
  • 去空格 rtrim 函数
  • select rtrim(vend_name) + '(' + rtrim(vend_country) + ')'
    from Vendors
    order by vend_name;
    

    rtrim 函数 -- 去掉值右边的所有空格

    ltrim 函数 -- 去掉值左边的所有空格

    trim 函数 -- 去掉值左右两边的所有空格

  • 使用别名 as
  • 别名(alias)是一个字段或值的替换名。

    别名使用 as 关键字赋予。

    select rtrim(vend_name) + '(' + rtrim(vend_country) + ')' as vend_title
    from Vendors
    order by vend_name;
    

    as 关键字是可选的,不过最好使用它。

    导出列(derived column)

  • 执行算术计算
  • select prod_id,
           quantity,
           item_price,
           quantity * item_price as expanded_price
    from OrderItems
    where order_num = 20008;
    

    基本算术操作符:

    +-*/ 加减乘除、圆括号区分优先级

    select 3 * 2; -- 测试数据
    

    省略 from 子句之后就是简单的访问和处理表达式。

    select now(); -- 返回当前时间
    

    第8课 使用数据处理函数

    不同的 DBMS 支持不同的函数,如何使用函数。

    利用函数处理数据,例如:rtrim() 函数可以去掉字符串空格。

  • 取字符串 mid() substring() sbustr()
  • 数据类型转换 convert() cast()
  • 取当前日期 curdate() sysydate() now() current_date() getdate() date()
  • SQL 函数不是可移植的。

    可移植(portable):所编写的代码可以在多个系统上运行。

    使用函数,应该做好代码注释。

  • 文本字符串:删除、填充、转换大小写
  • 算术运算:绝对值、代数运算
  • 日期、时间:时间差、时间有效性
  • 系统函数:登录用户等
  • 文本处理函数
  • rtrim() 函数:去除多余空格

    upper() 函数:转换为大写

    select vend_name,
           upper(vend_name) as vend_name_upcase
    from Vendors
    order by vend_name; -- 将文本转换为大写
    

    常用文本处理函数:

    left() 函数:返回字符串左边字符 -- Oracle 不支持

    length() 函数:返回字符串长度 -- 同 len()datalength() 函数

    upper() 函数:返回字符串大写 -- Access 使用 ucase() 函数

    lower() 函数:返回字符串小写 -- Access 使用 lcase() 函数

    ltrim()函数:去掉字符串左边空格

    right()函数:返回字符串右边字符

    rtrim() 函数:去掉字符串右边空格

    soundex() 函数:返回字符串的 soundex 值

    soundex 是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。考虑其类似发音字符、音节,使其能对字符串进行发音比较而不是字母比较。

    Microsoft Access 和 PostgreSQL 不支持 soundex( ) 函数。

    SQLite 必须指定 SQLite_soundex 编译选项才支持。

    select soundex('Michael Green'),
           soundex('Michelle Green')
    from dual;  -- 发音模糊过滤
    -- 1	M242	M242
    

    Oracle 中没有 leftright 的写法,但可以使用 substr 函数进行字符截取处理。例如:

    select substr('abcdefg', 1,5) from dual;
    -- abcde
    
  • 日期和时间处理函数
  • 日期和时间采用相应的数据类型存储在表中。

    不同的 DBMS 采用不同的时间日期处理函数,可移植性最差。

    -- SQL Server
    select order_num from Orders
    where datepart(yy, order_date) = 2012;
    -- Access
    select order_num from Orders
    where datepart('yyyy', order_date) = 2012;
    -- PostgreSQL
    select order_num from Orders
    where date_part('year', order_date) = 2012;
    -- Oracle
    select order_num from Orders
    where to_number(to_char(order_date, 'yyyy')) = 2012; -- 提取日期 转换为数值 进行对比
    -- Oracle between
    select order_num from Orders
    where order_date between to_date('01-01-2012')
    and to_date('12-31-2012');
    -- MySQL MariaDB
    select order_num from Orders
    where year(order_date) = 2012;
    -- SQLite
    select order_num from Orders
    where strftime('%Y', order_date) = '2012';
    

    datepart() 函数:返回日期的某一部分

    大多数 DBMS 具有比较日期、执行基于日期的运算、选择日期格式等函数。

  • 数值处理函数
  • 数值处理函数仅处理数值数据。

    代数、三角或几何运算。

    在各 DBMS 中,数值函数反而是最一致、最统一的函数。

    常用数值处理函数:

    abs() 返回绝对值

    cos() 返回余弦值

    sin() 返回正弦值

    tan() 返回正切值

    exp() 返回指数值

    pi() 返回圆周率

    sqrt() 返回平方根

    第9课 汇总数据

    利用 SQL 聚集函数汇总表的数据。

    汇总表中的数据,而不需要实际数据本身。

    聚集函数(aggregate function):对某些行运行的函数,计算并返回一个值。

    avg() 平均值

    count() 计数

    max() 最大值

    min() 最小值

    sum() 求和

    avg() 函数只能用于单个列,确定特定数值列的平均值。多个列必须使用多个 avg() 函数。

    avg() 函数忽略列值为 NULL 的行。

    使用 count(*) 对表中行进行计数,不管列中是否为空值(NULL)还是非空值。

    使用 count(column) 对特定列进行计数,忽略 NULL 值。

    对非数值数据使用 max() 函数,返回排序后最后一行。忽略 NULL 行。

    对非数值数据使用 min() 函数,返回排序后最前面一行。忽略 NULL 行。

    select count(*) as num_items,
           min(prod_price) as price_min,
           max(prod_price) as price_max,
           avg(prod_price) as price_avg
    from Products; -- 组合聚集函数
    
  • 聚集不同值
  • 对所有行执行计算,指定 ALL 参数或不指定参数(因为 ALL 是默认行为)。

    只包含不同的值,指定 distinct 参数。

    Access 中不支持 distinct,必须使用子查询把 distinct 结果返回到外部查询。

    select avg(distinct prod_price) as avg_price
    from Products
    where vend_id = 'AAAA';
    

    distinct() 必须指定列名

    有的 DBMS 支持其他参数,例如:top()top percent() 等。

    别名应该采用新的名称,而不该使用已有的列名。

    聚集函数很高效,比客户端计算快得多。

    第10课 分组数据

    分组数据,汇总表内容的子集。

    select 语句子句:group by 子句和 having 子句。

    使用 SQL 聚集函数可以汇总数据。

    使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。

    select vend_id,
           count(*) as num_prods
    from Products
    group by vend_id;
    

    group by 子句指示 DBMS 分组数据,然后对每个组而不是整个结果集进行聚集。

  • group by 子句可以包含任意数目的列,可以对分组进行嵌套,更细致的进行数据分组。
  • 如果分组列中包含 NULL 值的行,则 NULL 将作为一个分组返回。
  • group by 子句必须出现在 where 子句之后,order by 子句之前。
  • SQL Server 支持可选的 ALL 子句,返回所有的分组。

    也可以通过相应位置指定列,例如: group by 2,1

    除了能用 group by 分组数据外,SQL 还允许过滤分组。

    where 没有分组的概念。

    必须使用 having 子句,类似于 where ,唯一的区别是 where 过滤行,而 having 过滤分组。

    select cust_id,
           count(*) as orders
    from Orders
    group by cust_id
    having count(*) >= 2; -- 过滤
    

    where 子句在这里不起作用,因为过滤是基于分组聚集值,而不是特定行的值。

    where 在数据分组前进行过滤,而 having 在数据分组后进行过滤。

    select vend_id,
           count(*) as num_prods
    from Products
    where prdo_price >= 4
    group by vend_id
    having count(*) >= 2;
    

    使用 having 时应该结合 group by 子句。而 where 子句用于标准的行级过滤。

  • 分组和排序
  • order by 对产生的输出排序,任意列都可以使用。

    group by 对行分组,但输出可能不是分组的顺序。

    使用 order by 排序保证数据正确排序的唯一方法。

    select order_num,
           count(*) as items
    from OrderItems
    group by order_num
    having count(*) >= 3
    order by items, order_num; -- 先分组 后过滤 再排序
    

    Access 不允许按别名排序,需要用实际的字段替代,例如:order by count(*), order_num。

  • select 子句顺序
  • 在使用 select 子句时必须遵循的次序。

    是否必须使用

    完全限定列名。

    联结的实际操作是将2个表的每一行都进行匹配,而 where 子句则是过滤满足条件的行。

    笛卡尔积:(cartesian product)检索的数目为2个表行数相乘。

    select vend_name,
           prod_name,
           prod_price
    from Vendors a,
         Products b; -- 产生 N 行
    

    因此,不要忘了 where 子句。

    返回笛卡尔积的联结,也称为叉联结(cross join)。

    等值联结(equijoin),基于两个表之间的相等条件。

    也称为内联结(inner join)。

    select vend_name,
           prod_name,
           prod_price
    from Vendors a
    inner join Products b
    on a.vend_id = b.vend_id;
    

    传递给 on 的实际条件与传递给 where 相同。

    ANSI SQL 规范首选 inner join 语法。

  • 联结多个表
  • SQL 不限制可以联结表的数目,在实际生产中,PostgreSQL 能关联10个表以上。

    select prod_name,
           vend_name,
           prod_price,
           quantity
    from OrderItems a, 
         Products b,
         Vendors c
    where b.vend_id = c.vend_id
    and a.prod_id = b.prod_id
    and order_num = 20007; -- 多表关联
    

    where 子句定义联结条件,过滤条件。

    性能问题:联结很消耗资源,联结越多,性能越低。

    第11课中的子查询写法可以使用联结写法替代。

    性能可能会受操作类型、所使用的的 DBMS、表中数据量、是否存在索引或键等条件的影响。

    第13课 创建高级联结

    其他联结,表别名,对联结的表使用聚集函数等。

  • 使用表别名
  • 缩短 SQL 语句

    允许在一条 select 语句中多次使用相同的表

    使用 as 作为别名

    表别名不返回至客户端,不像列别名

  • 不同类型的联结
  • self-join 自联结

    natural join 自然联结

    outer join 外联结

    使用表别名可以在一条 select 中多次使用同一个表。

    select cust_id,
           cust_name,
           cust_contact
    from Customers
    where cust_name = (select cust_name
                      from Customers
                      where cust_contact = 'Jim Jones');
    

    通过子查询查找同公司下的用户。也可以使用自联结:

    select a.cust_id,
           a.cust_name,
           a.cust_contact
    from Customer a,
         Customer b
    where a.cust_name = b.cust_name
    and a.cust_contact = 'Jim Jones'; -- 简洁
    

    使用表别名。联结后过滤。

    标准的联结返回所有数据,相同的列甚至多次出现。自然联结排除多次出现,去重。

    select a.*
           b.order_num,
           b.order_date,
           c.prod_id,
           c.quantity,
           c.item_price
    from Customers as a,
         Orders as b,
         OrderItems as c
    where a.cust_id = b.cust_id
    and c.order_num = b.order_num
    and prod_id = 'RGAN01';
    

    第一个表使用通配符,其他表数据指定具体列。

    包含那些在相关表中没有关联行的行,这种联结成为外联结。

    select a.cust_id,
           b.order_num
    from Customers a
    left outer join Orders b  -- 左连接
    on a.cust_id = b.cust_id;
    

    在使用 outer join 语法时,必须使用 right 或者 left 关键字指定包括其他行的表。

    分别表示包含左、右数据表的所有数据行。

    SQLite 支持 left outer join,但不支持 right outer join。

  • 全外联结(full outer join)
  • 检索两个表中的所有行并关联那些可以关联的行。

    select a.cust_id,
           b.order_num
    from Customers a
    full outer join Orders b
    on a.cust_id = b.cust_id;
    

    另外一种 cross jon 连接。

  • 使用带聚集函数的联结
  • 聚集函数用来汇总数据。

    select a.cust_id,
           count(b.order_num) as num_ord
    from Customers a
    inner join Orders b
    on a.cust_id = b.cust_id
    group by a.cust_id;
    

    也可以与其他联结一起使用。

  • 切记检查联结的结果,记得测试后使用!!!
  • 添加联结条件,否则会出现笛卡尔积。
  • 减少多个表联结,减少故障。
  • 第14课 组合查询

    利用 union 操作符将多条 select 语句组合成一个结果集。

    组合查询通常被称为并(union)或者复合查询(compound query)。

    select cust_name,
           cust_contact,
           cust_email
    from Customers
    where cust_state in ('A', 'B', 'C')
    union
    select cust_name,
           cust_contact,
           cust_email
    from Customers
    where cust_name = 'xxxx';
    

    union 指示 DBMS 执行这两条 select 语句,并把输出组合成一个查询结果集。

    使用 union 组合 select 语句的数目具体没有限制。

    注意考虑一下性能问题。

  • union 规则
  • union 必须由两条或以上语句组成;
  • 每个查询必须包含相同的列、表达式、聚集函数;
  • 列数据类型必须兼容,例如:不同的数值类型;
  • union 从查询结果集中自动去除了重复的行。

  • union all
  • 返回所有匹配行,不去重。

    select cust_name,
           cust_contact,
           cust_email
    from Customers
    where cust_state in ('A', 'B', 'C')
    union all
    select cust_name,
           cust_contact,
           cust_email
    from Customers
    where cust_name = 'xxxx'; -- 不去重
    

    使用 union 组合查询时,只能使用一条 order by 子句,且必须位于最后一条 select 语句之后。

    select xxx from xxx
    union all
    select xxx from xxx
    order by xxx; -- 排序所有结果
    

    某些 DBMS 还支持另外两种 union:

  • except (minus) -- 只存在1表,不存在2表
  • intersect -- 都存在 取交集
  • 使用 union 还可以组合不同表查询,结合别名使用。

    第15课 插入数据

    使用 insert 语句将数据插入表中。

  • 插入完整的行
  • 插入行的一部分
  • 插入某些查询的结果
  • 使用 insert 语句进行插入需要特定的操作权限。

    insert into Customers
    values('100','Toy','123',NULl,100);
    

    必须给每一列提供一个值。如果某列没有值,必须使用 NULL 值填充,而且顺序应该依次出现。

    into 关键字可选?某些 DBMS 支持。

    这种语法适用于测试,单独插入某条数据时使用。不适用于大批量更新。

    insert into Customers (cust_id, cust_name, cust_address, cust_zip, cust_email)
    values('100','Toy','123',NULl,100);
    

    明确指出列名,因此无须按顺序出现。

    即使表结构发生变化,也不影响。

    values 数目必须正确,才能成功插入。

  • 插入部分行
  • 只给某些列提供值,给其他列不提供值。

    insert into Customers(cust_id, cust_name)
    values('100', 'Toy'); -- 插入部分行
    

    省略的列将自动插入空值 NULL。

    列必须允许为空。

  • 插入检索出的数据
  • insert select 语法也是合法的。

    insert into Customers(cust_id, cust_name,xxxx,xxx,xxx)
    select cust_id,
           cust_name,
           xxxx,
    from CustNew;
    

    亲测:Oracle可使用!

    insert select 中可以包含 where 子句,以过滤插入的数据。

    可以插入多行。

  • 从一个表复制到另一个表
  • 创建新的表,可以使用 select into 语句。

    表不存在,新建。

    insert select -- 导出数据
    select into -- 导入数据
    
    select * into CustCopy
    from Customers;
    

    MariaDB、MySQL、Oracle、PostgreSQL 和 SQLite 使用以下语法:

    create table temp_xxx as
    select * from xxx;
    

    可以结合 where 和 group by 进行使用,可以联结多个表插入数据,但只能插入一个表。

    第16课 更新和删除数据

    使用 update 语句更新数据。

    不要省略 where 子句。

    更新需要特定的安全权限。

    update Customers
    set cust_email = 'xxxxxx'
    where cust_id = '100';
    

    set 关键字用于将新值赋给被更新的列。

    没有 where 将更新所有的行。

    update Customers
    set cust_email = 'xxxxxx',
        cust_contact = 'Sam'
    where cust_id = '100';
    

    利用键-值对更新多个值。

    在 update 语句中还可以使用子查询。

    使用 from 子句,用一个表的数据更新另一个表的行。

    要删除某个列的值,可设置为 NULL。

    update Customers
    set cust_email = NULL
    where cust_id = '200';
    

    NULL 与空字符串不同。

    使用 delete 语句删除数据。

    不要省略 where 语句。

    先通过 select 测试子集之后再修改为 delete 语句进行数据行的删除。

    delete from Customers
    where cust_id = '100';
    

    where 进行数据过滤。

    友好的外键:可以使用外键来严格实施关系,实施引用完整性。防止 delete 语句删除必要的数据。

    delete 是删除整行数据,因此不需要列名或者通配符。

    删除指定列可以通过 update 进行。

    delete 不删除表本身。

  • 删除所有行
  • delete 删除部分行

    更快的清空所有行可以使用 truncate table 语句,速度更快,因为不记录数据的变动。

  • 记得带 where 子句;
  • 先 select 测试,后 delete 删除;
  • 使用强制实施引用完整性的数据库;
  • 确认之后再提交,commit;
  • 第17课 创建和操纵表

    创建、更改和删除表。

    SQL 还可以用来执行数据库和表的所有操作。

    通过 create table 语句。

    通过交互式界面工具创建表会自动生成并执行相应的 SQL 语句。

    create table Products
    	prod_id char(10) not null,
        vend_id char(10) not null,
        prod_name char(254) not null,
        prod_price decimal(8,2) not null,
        prod_desc varchar(1000) null
    ); -- 数据类型及兼容性
    

    不同 DBMS ,必须书写不同的语句。

    语句格式化:多行、缩进。

    每个表列要么是 NULL 列,要么是 not NULL 列。NULL 为默认设置。

    只有不允许 NULL 值的列可作为主键、唯一标识。

    注意NULL 值 && 空字符串''的区别。

    指定默认值:使用关键字 default 指定。

    create table temp_cccc
    	order_num integer not null,
        quantity integer not null default 1
    

    默认值经常用于日期或时间戳列。引用系统日期的函数或变量,将系统日期用作默认日期。

    -- 建表
    create table temp_cccccc
      id integer not null,
      money varchar(10),
      date2 date default sysdate  -- 系统时间 默认值
    -- 插入数据
    insert into temp_cccccc(id) values(2)
    -- 查询
    select * from temp_cccccc
    -- 1	2		2020/1/17 16:56:53
    
    -- 默认获取系统日期
    default current_date() -- MySQL
    default sysdate -- Oracle
    default getdate() -- SQL Server
    default now() -- Access
    

    使用 default 而不是 NULL 值,有利于计算和数据分组。

    更新表定义,可以使用 alter table 语句。

    不要在表中包含数据时对其进行更新。

    对表增加列的数据类型(以及 NULL 和 default 的使用)有所限制。

    重命名表中的列。

    修改表结构:表名,列。

    -- 新增一个列
    alter table temp_cccccc
    add data_desc varchar2(10);
    

    更改或删除列、增加约束或增减键,使用类似语法。

    -- 删除一个列
    alter table temp_cccccc
    drop column money;
    

    以下操作为 Oracle 中常用的修改表结构用法,注意部分操作需要字段内容为空时方可操作。

    ------------ Oracle 更新表结构 ------------
    -- 1.添加字段
    alter table temp_xxx add col_name varchar2(10);
    -- 2.删除字段
    alter table temp_xxx drop column col_name;
    -- 3.修改字段类型
    alter table temp_xxx modify col_name integer;
    -- 4.添加备注
    comment on column 库名.表名.字段名 is '输入的备注';
    

    复杂的表结构修改一般需要手动删除,重新创建表。

    再根据需要,重新创建触发器、存储过程、索引和外键。

    alter table 进行前备份表,防止意外。

    使用 drop table 语句删除表。

    drop table temp_cccc;
    

    永久删除改表。

    drop table temp_ccccc purge; -- 删除并清空回收站
    

    启用关系规则防止意外删除有用的表。

    使用 rename 语句进行重命名表名。

    rename temp_cccccc to temp_xxx; -- 重命名
    alter table temp_xxx rename to temp_yyy; -- 也可以
    create table temp_zzz as
    select * from temp_yyy;
    drop table temp_yyy; -- 备份再删除
    

    切记:备份!!备份!!备份!!

    第18课 使用视图

    视图是虚拟的表,与包含数据的表不同,视图只包含使用时动态检索数据的查询。

    利用视图可以简化某些 SQL 操作。

    Microsoft Access 不支持视图。

    任何需要明细数据的人不可能都要详细的去了解表结构,如何关联和对表进行联结。

    作为视图,包含的只是查询

  • 重用 SQL 语句
  • 简化复杂的 SQL 操作
  • 使用表的一部分而不是整个表
  • 更改数据格式和表示
  • 可以对视图执行 select 操作,过滤和排序数据,将视图联结其他视图或表,甚至添加和更新数据。

    性能问题:每次使用视图都必须处理查询执行时需要的所有检索。

    视图必须唯一命名。

    创建视图,必须具有足够的访问权限。

    视图可以嵌套。

    许多 DBMS 禁止在视图查询中使用 order by 子句。 -- 测试一番? Oracle 可以排序。

    如果列是计算字段,需要使用别名,例如:count(*) as cnt。

    视图不能索引、也不能有关联的触发器或默认值。

    使用 create view 语句创建视图。

    使用 drop view view_name 删除视图。

    利用视图简化复杂的联结。

    create view ProductCustomers as
    select cust_name,
           cust_contact,
           prod_id
    from Customers a,
         Orders b,
         OrderItems c
    where a.cust_id = b.cust_id
    and b.order_num = c.order_num;  -- 创建视图
    

    使用查询。

    select * from ProductCustomers
    where prod_id = 'xxx';
    

    但 DBMS 处理查询时,将指定的 where 子句添加到视图查询中已有的 where 子句中,正确的过滤数据。

    利用视图,可以一次性编写基础的 SQL,然后根据需要多次使用。

    视图需要进行维护。

    另一用途是重新格式化检索的数据。

    create view VendorLocations as
    select rtrim(vend_name) || '(' || rtrim(vend_country) || ')' as vend_title
    from Vendors;
    

    创建视图之后,通过 where 过滤数据。

    视图内与传递给视图的 where 子句将自动组合。

    使用视图与计算字段。

    create view OrderItemsExpanded as
    select order_num,
           prod_id,
           quantity,
           item_price,
           quantity * item_price as expanded_price
    from OrderItems;
    

    正确使用视图,可以极大地简化复杂数据的处理。

    视图为虚拟的表。

    重新格式化或保护基础数据。

    第19课 使用存储过程

    存储过程是什么?为甚么要使用存储过程?如何使用?创建和使用存储过程的基本语法。

    存储过程(Stored Procedure) 是在大型数据库系统中,一组为了完成特定功能的 SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来调用存储过程。

    针对许多表的多条 SQL 语句,多种判断,有条件的执行其他语句。

    可以视为批文件。

    Microsoft Access 和 SQLite 不支持存储过程。

  • 为什么要使用存储过程
  • 通过把处理封装在一个单元中,可以简化复杂的操作。
  • 保证数据的一致性,防止错误。
  • 修改方便,表名变动只需修改一次。安全性。
  • 存储过程通常是以编译过的形式存储,所以 DBMS 处理命令所需工作量少,提高性能。
  • 灵活使用。
  • 简单、安全、高性能!!!

    缺陷:不同 DBMS 语法不同,可移植性差!

  • 执行存储过程
  • 通过 execute 执行存储过程,execute 接受存储过程名和需要传递的任何参数。

    execute procedure_name('a','b','c','d');
    

    执行存储过程,插入数据。ID 自动生成。

  • 参数可选,具有不提供参数时的默认值;
  • 不安次序给出参数,以“参数=值”的方式给出参数值;
  • 输出参数;
  • 用 select 检索数据;
  • 返回代码;
  • 创建存储过程
  • -- Oracle -- 执行不通过 待修改
    create procedure MailListCount (
    	ListCount out integer  -- 返回一个值
    v_rows integer;
    begin
    	select count(*) into v_rows
    	from Customers
    	where cust_email is not null;
    	ListCount := v_rows;
    

    in -- 传递值给存储过程

    out -- 从存储过程返回值

    inout -- 既输入又输出

    begin、end -- 存储过程代码

    执行存储过程。

    -- Oracle --
    call MailListCount();
    -- 或者 --
    var ReturnValue number
    exec MailListCount(:ReturnValue);
    select ReturnValue;
    

    declare 语句声明变量

    @开头 为局部变量

    Return @cnt 返回调用程序

    -- SQL Server --
    create procedure MailingListCount
    declare @cnt integer
    select @cnt = count(*) 
    from Customers
    where cust_email is not null;
    return @cnt;
    -- 调用 --
    declare @ReturnValue int
    execute @ReturnValue = MailingListCount;
    select @ReturnValue;
    

    另外的例子,在数据库表中插入数据。

    -- SQL Server --
    create procedure NewOrder @cust_id char(10)
    declare @order_num integer
    select @order_max = max(order_num)
    from Orders
    select @order_num = @order_num + 1 -- 自动生成
    insert into Orders(order_num,
                       order_date,
                       cust_id)
           values(@order_num,
                  getdate(),
                  @cust_id)
    return @order_num;
    

    局部变量保存最大订单号,和系统日期。

    多加注释。

    -- SQL Server --
    create procedure NewOrder @cust_id char(10)
    insert into Orders(cust_id)
    values(@cust_id)
    select order_num = @@identity;
    

    自动增量的列为标识字段(identity field),或者自动编号(auto number)或序列(sequence)。

    结合 DBMS 特性进行编写存储过程。

    第20课 管理事务处理

    利用 commit 和 rollback 语句管理事务处理。

    使用事务处理(transaction processing),通过确保成批的 SQL 操作要么完全执行,要么完全不执行,来维护数据库的完整性。

    设计良好的数据库模式,通过主键的唯一ID互相关联。

    假设由于某种数据库故障(超出磁盘空间、安全限制、表锁等),插入数据过程无法完成,将该如何?

    —— 事务处理,是一种机制。

    如果没有错误发生,整组语句提交给数据库表;

    如果发生错误,则进行回退(撤销),将数据库恢复到某个已知且安全的状态;

  • 事务(transaction)指一组 SQL 语句;
  • 回退(rollbak)指撤销指定 SQL 语句的过程;
  • 提交(commit)指将未存储的 SQL 语句结果写入数据库表;
  • 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),可以对它发布回退;
  • 事务处理用来管理 insert、update 和 delete 语句。

    不能回退 select 语句,也没必要。

    不能回退 create 和 drop 操作。

  • 控制事务处理
  • 管理事务的关键在于将 SQL 语分解为逻辑块,并明确规定数据何时该回退?何时不应该回退?

    -- SQL Server --
    begin transaction
    commit transaction
    

    中间的 SQL 必须完全执行或者完全不执行。

    -- MariaDB or MySQL --
    start transaction
    
    -- Oracle --
    set transaction
    -- PostgreSQL --
    begin
    

    事务一直存在,没有明确标识事务结束,直到被中断。

    通常,commit 用于保存更改,rollback 用于撤销。

    select * from temp_cwh_test;
    delete from temp_cwh_test;
    rollback; -- 回滚
    

    一般的 SQL 语句都是针对数据库表直接执行和编写的,这就是所谓的隐式提交(implicit commit),即提交操作是自动进行的。

    -- SQL Server --
    begin transaction
    delete from OrderItems where order_num = 12345
    delete from Orders where order_num = 12345
    commit transaction -- 保证2条语句都执行成功时才提交
    -- Oracle --
    set transaction
    commit;
    
  • 使用保留点
  • 复杂的事务可能需要部分提交或回退。

    要支持回退部分事务,必须在事务处理块中的合适位置放置占位符。

    在 SQL 中,这些占位符称为保留点。

    创建占位符,可以使用 savepoint 语句。

    -- MariaDB/MySQL/Oracle --
    savepoint deletel;
    -- SQL Server --
    save transaction deletel;
    

    每个保留点都要有能够标识的唯一名字。

    -- SQL Server --
    rollback transaction deletel;
    -- Oracel --
    rollback to deletel;
    

    完整的例子:

    begin transaction
    insert into Customers(cust_id, cust_name)
    values('100','Toy');
    save transaction StartOrder;
    insert into Orders(order_num, order_date, cust_id)
    values(200,'2020/12/1','100');
    if @@error <> 0 rollback transaction StartOrder;
    insert into OrderItems(order_num, order_item, prod_id, quantity, item_price)
    values(100,1,'ds',100,5,49);
    if @@error <> 0 rollbak transaction StartOrder;
    insert into OrderItems(order_num, order_item, prod_id, quantity, item_price)
    values(100,2,'xx',100,10.99);
    if @@error <> 0 rollback transaction StartOrder;
    commit transaction
    

    定义保留点,如果操作失败则事务处理能够回滚。

    保留点越多越能灵活处理。

    事务是必须完整执行的 SQL 语句块。利用 commit 和 rollback 语句进行管理。保留点。

    第21课 使用游标

    如何使用游标?

    结果集(result set):SQL 查询所检索出的结果。

    游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,不是一条 select 语句,而是被该语句检索出来的结果集。

    有了游标之后,可以根据需要滚动或浏览其中的数据。

    能够标记游标只读,使数据能读取,不能更新和删除。

    能够控制定向操作(向前、向后、第一、最后、绝对位置、相对位置等)。

    Microsoft Access 不支持游标。

    SQLite 支持的游标称为步骤(step)。

    声明游标,定义 select 语句和游标选项。

    打开游标以供使用。

    根据需要检索各行。

    结束游标使用后,关闭游标,释放游标。

    使用 declare 语句创建游标。

    -- MySQL --
    declare CustCursor cursor
    select * from Customers
    where cust_email is null;
    -- Oracle --
    declare cursor CustCursor
    select * from Customers
    where cust_email is null;
    

    使用 open cursor 语句打开游标。

    open cursor CustCursor;
    

    执行查询,存储检索出的数据以供浏览和滚动。

    利用 fetch 语句访问游标数据。

    -- Oracle --
    declare type CustCursor is ref cursor
    	return Customers%ROWTYPE;
    declare CustRecord Customers%ROWTYPE
    begin
    	open CustCursor;
    	fetch CustCursor into CustRecord;
    	close CustCursor;
    end; -- fetch 检索当前行,放到声明的变量中,不做任何处理
    
    -- Oracle --
    declare type CustCursor is ref cursor
    	return Customers%ROWTYPE;
    declare CustRecord Customers%ROWTYPE
    begin
    	open CustCursor;
    		fetch CustCursor into CustRecord;
    		exit when CustCursor%NOTFOUND;
    	end loop;
    	close CustCursor;
    end; -- 循环所有行
    
    -- SQL Server --
    declare @cust_id char(10),
            @cust_name char(50),
            @cust_address char(50),
            @cust_city char(50),
            @cust_state char(50),
            @cust_zip char(10),
            @cust_email char(255)
    open CustCursor
    fetch next from CustCursor
    into @cust_id,@cust_name,@cust_address,@cust_city,@cust_state,@cust_zip,@cust_email
    while @@fetch_status = 0
    begin
    	fetch next from CustCursor
    		into @cust_id,@cust_name,@cust_address,@cust_city,@cust_state,@cust_zip,@cust_email
    close CustCursor
    

    声明变量,fetch 语句检索一行并保存。

    while 循环处理下一行。

    释放游标所占用的资源。

    -- Oracle --
    close CustCursor;
    -- SQL Server --
    close CustCursor
    deallocate cursor CustCursor;
    

    先声明,后打开,再使用,记得关闭!

    第22课 高级 SQL 特性

    高级数据处理特性:约束、索引和触发器。

    关联表和引用完整性(referential integrity)。

    关系数据库设计

    数据库完整性规则

    约束(constraint):管理如何插入或处理数据库数据的规则。

    DBMS 通过在数据库表上施加约束来实施引用完整性。

    通过使用 create table 或者 alter table 语句。

    特殊的约束,保证一列中的值是唯一的,而且永不改动。

    唯一标识每一行。

    任意两行的主键值都不相同。

    主键值唯一,且不为 NULL 值。

    主键值从不修改或更新。

    主键值不能重用。

    create table Vendors
      vend_id char(10) not null primary key,
      vend_name char(50) not null,
      vend_state char(50) null,
      vend_country char(50) null
    

    添加关键字 primary key ,使其成为主键。

    alter table Vendors
    add constraint primary key (vend_id); -- 添加约束
    

    SQL Lite 不允许使用 alter table 定义键,要求初始的 create table 语句中定义。

    外键是表中的一列,其值必须在另一表的主键中。

    外键是保证引用完整性的重要部分。

    create table Orders
    	order_num integer not null primary key,
        order_date datetime not null,
        cust_id char(10) not null references Customers(cust_id)
    

    使用 references 关键字,建立依赖。或者:

    alter table Orders
    add constraint
    foreign key (cust_id) references Customers (cust_id);
    

    外键有助于防止意外删除。

    有的 DBMS 支持级联删除(cascading delete)的特性。

    用来保证一列中的数据是唯一的,类似于主键。但存在区别:

    表可以多个唯一约束,但只允许一个主键。

    可包含 NULL 值。

    可修改或更新。

    可重复使用。

    不能用来定义外键。

    可以通过在列上定义 unique 约束做到,也可以单独使用 constraint 定义。

    检查约束保证一列中的数据满足一组指定的条件。

    检查最小、最大值。

    指定范围。

    只允许特定的值。

    DBMS 会拒绝任何无效的数据。

    create table OrderItems
    	order_num integer not null,
        order_item integer not null,
        quantity integer not null check (quantity >0)
    
    add constraint check(gender like '[MF]')
    

    用户定义数据类型。

    索引用来排序数据以加快搜索和排序操作的速度。

    数据库的索引。类似于书籍的目录。

    主键数据总是排序的,因此按主键检索特定行总是一种快速有效的操作。

    可以在一个或多个列上定义索引,使 DBMS 保存内容为一个排序的列表。

  • 索引改善检索操作的性能,但降低了数据插入、修改和删除的性能;执行这些操作时,DBMS 必须动态地更新索引;
  • 索引数据可能要占用大量的存储空间;
  • 不是所有数据都适合做索引;
  • 索引用于数据过滤和数据排序;
  • 可以定义多个列,组合索引;
  • 索引使用 create index 语句创建。

    create index prod_name_ind
    on Products(prod_name);
    

    索引必须唯一命名。

    定期检查索引,优化调整索引。

    触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。

    触发器可以与特定表上的 insert、update 和 delete 操作(或组合)相关联。

    触发器与单个表相关联。

    触发器可在特定操作执行之前或之后执行。

    保证数据一致。

    基于某个表的变动在其他表上执行活动。

    进行额外的验证并根据需要回退数据。

    -- SQL Server --
    create trigger customer_state
    on Customers
    for insert, update
    update Customers
    set cust_state = upper(cust_state)
    where Customers.cust_id = inserted.cust_id;
    
    -- Oracle --
    create trigger customer_state
    after insert or update
    for each row
    begin
    update Customers
    set cust_state = upper(cust_state)
    where Customers.cust_id = :OLD.cust_id
    

    约束比触发器更快。

  • 数据库安全
  • 用户访问权限

    用户授权和身份确认

    grant -- 授权
    revoke -- 回收权限
    

    总结:约束、索引、触发器、授权。

    基本数据库设计

    表中存放的信息

    表与表的关联

    行中数据分解

    玩具经销商使用的订单录入系统的组成部分:

  • 管理供应商
  • 管理产品目录
  • 管理顾客列表
  • 录入顾客订单
  • Vendors表——销售产品的供应商

    所有的表都应该有主键

    Products表——产品目录

    为实施引用完整性,应该定义外键,关联到Vendors表

    Customers表——顾客信息

    Orders表——顾客订单

    OrderItems表——订单的实际物品

    使用关系图 E-R图说明数据库表的关联方式。

  • 配置 ODBC 数据源
  • 使用 ODBC 进行数据库集成。

    ODBC 是一个标准,能使客户端应用与不同的后端数据库或基础数据库引擎交互。

  • create.txt 创建5个数据库表(定义所有的主键和外键约束)
  • populate.txt 填充数据库表的insert语句
  •