《SQL初学者指南》读书笔记

关系型数据库和SQL

  • SQL语言的三个部分
  • DML:Data Manipulation Language,数据操纵语言,检索、修改、增加、删除数据库(表)中的数据
  • DDL:Data Definition Language,数据定义语言,创建和修改数据库(表)本身
  • DCL:Data Control Language,维护数据库安全
  • 关系型数据库基本术语
  • relational(关系):表示各表彼此关联
  • record(记录):表中的行(row)
  • field(字段):表中的列(column)
  • primary key:表的主键,通常为自增型(auto-increment),本身没有特殊含义,只用于保证每一行都有一个唯一的值
  • foreign key:表的外键,确保这一列有一个有效的值,通常会把某个其他表的共同列,通常是主键作为外键,比如订单表中的客户列
  • column(field)的数据类型(更详细的数据类型介绍参见 SQL数据类型
  • bit:位,只允许0和1
  • integer:整数,不含小数位
  • decimal:浮点数,含小数位
  • real number:实数
  • 字符:string/character string类型,在SQL语句中需要用引号括起来
  • 日期/时间:用于表示日期和时间,在SQL语句中需要用引号括起来,允许对所涉及的日期进行特殊运算
  • NULL:空值,非数据类型,而是在允许包含空值的列中表示空置
  • 基本数据检索

  • SQL语句不区分大小写,且可以写成任意多行
  • 可以把重要关键字作为单独一行凸显语义
  • 从表中选择所有: SELECT * FROM tablename
  • 指定列: SELECT columnname FROM tablename
  • 指定多个列: SELECT column1, column2 FROM tablename 带空格的列名 :Microsoft SQL Server使用方括号 [] ,MySQL使用重音符`,Oracle使用双引号
  • 计算字段和别名

  • 直接量:与表中数据没有任何关系的值叫做literal value(直接量),字符串直接量需要加引号,数字直接量不需要
  • 算数运算:允许使用列数据与直接量或其它列数据进行加减乘除运算,比如 SELECT QuantityPurchased * PricePerItem FROM Orders
  • 连接字段:把字符数据连接或组合到一起,Microsoft SQL Server使用加号 + ,MySQL使用 CONCAT() 函数连接字符串,Oracle使用双竖线 ||
  • 列的别名:用于修改列(表头)标题或为计算字段提供列(表头)标题,比如 SELECT f_n AS 'first_name' from customers
  • 表的别名:通常有三种情况需要修改表名称,a.不好理解或复杂的表名,b.从多个表中进行选择,c.使用子查询; SELECE something from someshittablename AS another_table_name LEFT/RIGHT (string, numberOfCharactors) :从左/右取字符串数据的指定位数,在Oracle中以SUBSTR替代
  • SUBSTRING (string, start, end) :取得字符串数据的子字符串,在Oracle中以SUBSTR替代 LTRIM/RTRIM (string) :删除字符串数据左侧/右侧的空格 CONCAT (string1, string2, string3 ...) :拼接多个字符串,Oracle中只允许拼接两个字符串 UPPER/LOWER (string) :返回字符串的大/小写
  • 复合函数:函数的嵌套使用被称为复合函数,比如 RIGHT(RTRIM(something)) AS 'something'
  • 日期时间函数 GETDATE/NOW/CURRENT_DATE () :三个函数都用于获取当前时间,对应Microsoft SQL Server/MySql/Oracle三家数据库的实现
  • DATEPART (date_part, date_value) :单独返回某个部分的时间, date_part 为需要返回的时间部分, date_value 为原始时间,MySQL的实现为 DATE_FORMAT(date_value, date_format) , date_value 为原始时间, date_format 为类似于 %d 这样的格式用于告诉函数需要返回哪部分时间, date_part 的有效值为:year/quarter/month/dayofyear/day/month/hour/minute/second DATEDIFF (date_part, start_date, end_date) :用于计算任意两个不同日期间相差的时间,在MySQL中该函数之允许计算天数差异,所以 date_part 参数不可用,仅需要传入两个日期即可
  • 数值函数(数学函数) ROUND (NumbericValue, DecimalPlaces) :对任意数进行四舍五入, NumbericValue 表示要四舍五入的数, DecimalPlaces 表示从第几位开始四舍五入(即需要保留到第几位),以十分位为0,向左为负数,向右为正数
  • RAND ([seed]) :产生一个随机数 ,可选的 seed 参数为一个整数,用于每次返回相同的值 PI () :返回数学运算中的pi值 CAST (expression AS data_type) :将数据从一种类型转换为另一种类型, expression 表示数据或函数表达式, data_type 表示需要转换到的数据类型,一般情况下SQL在做计算时会进行自动的类型转换,所以很少用到这个函数,它的典型使用场景是当一个日期被保存成了字符串,需要转换为真正的日期数据: CAST('2017-5-1', AS DATETIME) ,Oracle中该函数的参数形式会不一样 ISNULL/IFNULL/NVL (column_data_maybe_null, if_null_will_use_this_data ) :将NULL值转换为一个有意义的值,对应Microsoft SQL Server/MySql/Oracle三家数据库的实现

    排序数据的语法如下:

    SELECT
    column1,
    column2
    FROM table1, table2
    ORDER BY column3, column2 DESC
    ORDER BY 句子总是在FROM子句之后,FROM子句总是在SELECT关键字之后
    SELECTORDER BY后面指定的列,可能是完全不同的一些列
    
  • 使用关键字ASCDESC来升序/降序排列
  • ORDER BY后指定了多列,则首先按照第一列排序,如遇相同则相同的行按第二列排序,以此类推
  • 根据计算字段排序时,如果计算字段已经出现在SELECT关键字后,则只需要在ORDER BY子句中指定计算字段的别名即可(经测试在MySQL中如果计算字段别名带有空格则暂时无法在此引用,因为不能使用引号),如果计算字段没有出现在SELECT关键字后,则可直接在ORDER BY子句中指定一个计算字段,例如:
    SELECT
    title,
    rental_duration,
    rental_rate
    FROM film
    ORDER BY rental_duration * rental_rate DESC
    
  • 按升序排列时,大部分的SQL数据库都会是按NULL(Oracle中排在最后,可使用NULLS FIRST关键字来强制最先)-数字-字符(字符中首先显示数字字符,再显示普通字符,除Oracle外不区分大小写)来进行排序,反之亦然。
  • 基于列的逻辑

  • 基于列的逻辑主要用于根据逻辑条件改变想要展现给用户的输出
  • 简单格式:判断某列的值为某个具体值后将其转换为一个其它值
  • SELECT column1, column2
    CASE column3
    WHEN value1 THEN result1
    WHEN value2 THEN value2
    (repeat WHEN-THEN any number of times)
    [ELSE defaul_result]
    column4
    FROM tablename
    
  • 查询格式:判断一列或多列中的某个值是否符合某个条件而将其转换为一个其它值并显示在一列中
  • SELECT
    WHEN condition1 THEN result1
    WHEN condition2 THEN  result2
    (repeat WHEN-THEN any number of times)
    [ELSE defaul_result]
    END AS custom_column_name,
    FROM tablename
    # 最新版的MySQL语法与书中的语法有细微差别:
    # ELSE子句最后不需要逗号
    SELECT
    title,
    WHEN rental_duration = 3 THEN 'Three Day Left'
    WHEN rental_rate = 0.99 THEN 'Cheapest'
    ELSE 'Normal'
    END AS 'Rental Status'
    FROM film
    

    基于行的逻辑

  • 基于行的逻辑主要用于获取满足特定条件的数据
  • 应用查询条件
  • SQL中的查询条件从WHERE子句开始
  • WHERE子句总是在FROMORDER BY 子句之间,实际上任何“子句”都必须按照这个顺序来
    SELECT columnList
    FROM tableList
    WHERE condition
    ORDER BY columnList
    
  • 使用TOP/LIMIT/ROWNUM(对应Microsoft SQL Server、MySQL和Oracle)限制行数(关键字TOP返回的行,并不是真的随机样本,而是根据数据库中的物理存储方式限定了前几行数据而已)
    # Microsoft SQL Server
    SELECT
    TOP number
    columnList
    FROM table
    # MySQL
    SELECT
    columnList
    FROM table
    LIMIT number
    # Oracle
    SELECT
    columnList
    FROM table
    WHERE ROWNUM <= number
    
  • 结合SORT BY子句做“Top N”查询(基于特定分类,得到带有最大/小值的一定数量的行)
    # 本月卖得最好的莎士比亚的三本书
    # MySQL
    SELECT
    title AS 'Book Title',
    current_month_sale AS 'Quantity Sold'
    FROM books
    WHERE author = 'Shakespear'
    LIMIT 3
    ORDER BY current_month_sale DESC
    # Oracle中的TOP N查询需要用到子查询,后文会细讲
    SELECT * FROM
    (SELECT
    title AS 'Book Title',
    current_month_sale AS 'Quantity Sold'
    FROM books
    ORDER BY current_month_sale)
    WHERE ROWNUM <= 3
    

    布尔逻辑(更复杂的基于行的逻辑)

  • 使用与AND、或OR、非NOT三个关键字在WHERE子句中表示布尔逻辑。与其它语言的计算顺序一样,AND的优先级最高,OR其次,NOT优先级最低,也可以使用()来改变三者的计算顺序
    # 这个例子仅为展示布尔逻辑,实际使用不应该这么绕
    SELECT
    first_name,
    last_name,
    FROM actors
    WHERE
    (age < 18 OR age > 60)
    AND last_name = 'Jhon'
    
  • 还有两个表示范围的操作符BETWEENIN,用于替代column >= range_bottom AND column <= range_topcolumn = value1 OR column = value2这样的特例,简化SQL语句的编写
    # BETWEEN,等价于 age >= 18 AND age <= 60
    SELECT
    first_name,
    last_name,
    FROM actors
    WHERE
    age BETWEEN 18 AND 60
    # IN,等价于 state = 'IL' AND state = 'NY'
    SELECT
    customer_name,
    state
    FROM orders
    WHERE state IN ('IL', 'NY')
    
  • 使用IS NULLWHERE子句中判断一个列是否为空,也可以与函数ISNULL(column, value)结合使用
    # 选取重量为0或者没有填写重量的产品
    SELECT
    product_description,
    weight
    FROM products
    WHERE weight = 0
    OR weight IS NULL
    # 使用ISNULL等价的写法
    SELECE
    product_description,
    weight
    FROM products
    WHERE ISNULL(weight, 0) = 0
    # IS NULL和ISNULL
    SELECT
    product_description,
    ISNULL(weight, 0) AS 'weight'
    FROM products
    WHERE weight = 0
    OR weight IS NULL
    WHERE子句可以使用LIKE操作符来查找针对列值的某部分匹配
    
  • 包含某关键字:
    SELECT 
      title
    WHERE
      title LIKE '%love%'
    
  • 以某关键字开头:
    SELECT
      title
    WHERE
      title LIKE 'love%'
    
  • 以某关键字结尾
    SELECT
      title
    WHERE
      title LIKE '%love'
    
  • 包含某关键字但不以其开头也不以其结尾(未能在MySQL 4中验证,只验证通过了单独的不以某字符串开头,或者不以某字符串结尾两种情况)
    SELECT
      title
    WHERE
      title LIKE '% love %'
    SUN(fee) AS 'Total Gym Fees'
    AVG(grade) AS 'Average Quiz Score'
    MIN(grade) AS 'Minimum Quiz Score'
    MAX(grade) AS 'Maximum Quiz Score'
    FROM grades
    WHERE grade_type = 'quiz'
    # 返回所有选中行的数目
    SELECT
    COUNT(*) AS 'Count of Homework Rows'
    FROM grades
    WHERE grade_type = 'homework'
    # 返回指定列中存在值的行的数目
    SELECT
    COUNT(grade) AS 'Count of Homework Rows'
    FROM grades
    WHERE grade_type = 'homework'
    # 与DISTINCT配合返回指定列中唯一值的行数
    SELECT
    COUNT(DISTINCT fee_type) AS 'Number of Fee Types'
    FROM Fees   
    
  • 分组数据:以指定列为依据对所有选中行进行分组,重新划分了行的显示规则
    # 统计每个分级下的电影数量
    SELECT 
    rating, COUNT(rating) AS 'Rating Count'
    GROUP BY rating
    
  • columnlist中的所有列,要么是GROUP BY子句中的列,要么是在聚合函数中使用的列,因为所有内容都在组中出现,不在组中的内容没有办法处理,这种情况下MySQL与其它两种数据库不同,它只会得出错误的结果,而不会报错
  • 多列分组:组的概念可以扩展,从而根据多列进行分组
    # 统计不同租金率下的不同分级电影的数量
    SELECT 
    rating, 
    rental_rate,
    COUNT(rating) AS 'Rating Count'
    FROM film
    GROUP BY rating, rental_rate    
    
  • 在没有分组的情况下,聚合函数(SUMAVGMINMAXCOUNT)统计的是所有行的数据,在有分组的情况下,这些聚合函数则仅会统计组内的数据,当然实际上也是最终显示的表的每一行的聚合
  • GROUP BY子句中的columnlist顺序没有意义,但ORDER BY子句中的顺序是有意义的,一般按照排序的优先顺序来列出这些列会很有帮助(也即SELECT中的columnlist与ORDER BY中的columnlist保持一致)
  • 基于分组应用查询条件:WHERE子句中的查询条件是针对单独的行来应用的,如果存在GROUP BY分组,就需要使用HAVING关键字了
    # 查看分级中所有电影平均时长大于100分钟的分级中电影的数量
    SELECT 
      rating AS '分级',
      COUNT(title) AS '电影数量',
      AVG(length) AS '平均时长'
    GROUP BY rating
    HAVING AVG(length) > 100
    ORDER BY 电影数量 DESC
    
  • 至此,SELECT语句的一般格式如下:
    SELECT
      columnlist
      tablelist
    WHERE
      condition
    GROUP BY
      columnlist
    HAVING
      condition
    ORDER BY
      COLUMNLIST
    

    用内连接来组合表

  • 关系型数据库最重要的成就是能够把数据组织到任意多个相互关联的表中,但同时这些又是彼此独立的;人们可以分析业务实体然后进行适当的数据库设计,这样就可以具有最大的灵活性;关系型数据库可以以任何你想要的方式把代表业务实体的表连接到一起,从而实现“关系”
  • 类似“客户”和“订单”这样两个独立的实体信息,至少应该要拆分到两个表中(订单表很有可能需要继续拆分成多个表),可以使用实体关系图(entity-relationship diagram)来表示可视化地表示两个表以及他们之间存在的隐性关系,实体(entity)指的是表,关系(relationship)指的是这些表中数据元素之间所画的线

  • 使用关键字INNER JOIN来指定想要连接的第二个表,使用ON来指定两个表的共同列由于共同列名称是一样的,所以需要在列名前面使用表名作为命名空间来区分两个表中独立的列
    # 简单地依据customer_id将顾客表和订单表拼接到一个表中
    SELECT *
    FROM customers
    INNER JOIN orders
    ON customers.customer_id = orders.customer_id
    
  • 内连接只会返回关联的两个表之间相匹配的数据,表在FROMINNER JOIN之间的顺序仅会影响哪个表的列先显示,不会影响行的顺序
  • SQL不是过程式语言,不会指定任务的先后顺序,而只是指定需要的逻辑并让数据库内部机制去决定如何执行任务。
  • 仅使用FROMWHERE也可以指定表的内连接,这是内连接的另一种范式,但因其没有显示地表示出连接的逻辑,所以不推荐使用(所以其实INNER JOIN ON的唯一作用仅仅是表达语义而已)
    SELECT *
    FROM customers, orders
    WHERE customers.customer_id = orders.customer_id
    
  • 可以通过显式地指定表的别名和列的别名(注意Oracle中表的别名与其他两个库的区别,前文有提及),来去除内连接后的重复列或者只显示需要的列,这是推荐的做法:
    SELECT
    c.customer_id AS 'Customer Id',
    c.first_name AS 'First Name',
    c.last_name AS 'Last Name',
    o.order_id AS 'Order Id',
    o.quantity AS 'Quantity',
    o.price_per_item AS 'Price'
    FROM customers AS 'c',
    INNER JOIN
    
  • 在实体关系图中,单向箭头表示表之间的连接是单向的,箭头终点的表中有一列所有行都能在箭头起点的表中找到,但反过来则不一定,比如,不是所有的客户都有订单,且一个客户可能有多个订单,但所有的订单都会有客户信息(甚至可以说所有的订单有且只有一个客户信息),退货信息与订单的关系类似
  • 当连接主表和从表时,我们需要主表中所有的行,即使在从表中的共同列没有任何行与之匹配
  • 使用IS NOT NULLIS NULL来过滤空行或显示空行
    # 过滤了没有订单的客户和有退货的订单
    SELECT
    customers.first_name AS 'First Name',
    customers.last_name AS 'Last Name',
    orders.order_date AS 'Order Date',
    orders.order_amount AS 'Order Amt'
    FROM customers
    LEFT JOIN orders
    ON orders.customer_id = customers.customre_id
    LEFT JOIN refunds
    ON orders.order_id = refunds.order_id
    WHERE orders.order_id IS NOT NULL
    AND refunds.refund_id IS NULL
    ORDER BY customers.customer_id, orders.order_id
    
  • 右连接与左连接唯一的不同就是主从表在关键字前后的位置,所以基本上没有必要使用右连接,建议只使用左连接,因为人直觉上往往认为先列出的表更为重要
  • 当设计有多个表的复杂FROM子句时,建议仅使用关键字LEFT JOIN并且避免使用圆括号
  • 全连接会显示所有行,即使没有在其他表中找到任何一个匹配。但在实际工作中很少会用到全连接,因为表之间的这种类型的关系是非常少见的。
  • 自连接和视图

  • 自连接:处理那些本质上是自引用的表(表中的一列指向自己的另一列,比如员工表中的manager列指向自己的employee_id,员工的经理也是员工),为其创建多个视图
  • 可以使用四种表连接中的任意一种来实现自连接,唯二的区别就是ON子句中,非自连接的共同列来自两个表,自连接的共同列来自同一个表,所以这时候需要在FROM关键字和JOIN关键字后为该表各自创建一个别名用以在ON子句中进行区分
  • # 列出personnel表中所有员工的经理名字
    SELECT
    employees.employee_name AS 'Employee Name',
    managers.employee_name AS 'Maneger Name'
    FROM personnel AS 'employees'
    LEFT JOIN personnel AS 'managers'
    ON employees.manager_id = managers._employee_id
    ORDER BY employee.employee_id
    
  • 视图只是保存在数据库中的SELECT语句,它不包含任何数据。
  • 随着时间的流逝,访问数据的需求会有所变化,但有时很难去重新组织数据库中的数据以满足新的需求。视图允许为数据库中已经存在的数据创建新的虚拟视图(或理解为虚拟的表)而无需重新组织数据,这为我们增加了始终能保持数据库设计不断更新的能力。
  • 因为视图没有保存物理数据,所以在视图中不能包含ORDER BY子句
  • # 创建视图的语法:
    CREATE VIEW view_name AS
    select_statement
    # 一个创建视图的例子,注意不能有ORDER BY子句
    CREATE VIEW customers_orders_refunds AS
    SELECT
    customers.first_name AS 'First Name',
    customers.last_name AS 'Last Name',
    orders.order_date AS 'Order Date',
    orders.order_amount AS 'Order Amt'
    FROM customers
    LEFT JOIN orders
    ON orders.customer_id = customers.customre_id
    LEFT JOIN refunds
    ON orders.order_id = refunds.order_id
    WHERE orders.order_id IS NOT NULL
    AND refunds.refund_id IS NULL
    
    # 创建视图
    CREATE VIEW view_name AS
    select_statement
    # 引用视图
    SELECT * from view_name
    
  • 当引用视图中的列的时候,需要指定列的别名,而列的别名是在创建视图时指定的
    # 创建视图
    CREATE VIEW customers_view AS
    SELECT
    first_name AS 'First Name',
    last_name AS 'Last Name'
    FROM customers
    # 引用视图中的列
    SELECT
    `First Name`,
    `Last Name`,
    FROM customers_view
    WHERE `Last Name` = 'Lopez'
    
  • 视图可以减少复杂度:将复杂的SELECT语句封装为一个视图
  • 视图可以增加复用性:封装那些总是相连的表
  • 视图可以正确地格式化数据:如果一个表中的某些数据总是需要格式化,可以将其封装到视图中
  • 视图可以创建计算的列:如果需要一个含有大量的计算字段的表,也可将其封装到视图中
  • 视图可以用来重新命名列的名称:如果一个表中的列名总是需要重命名,可以将其封装到视图中
  • 视图可以创建数据子集:如果总是只需要看到某个表的某些子集,可以将它们封装到不同的视图
  • 视图可以用来加强安全性限制:如果一个表中的某些数据希望对某些用户做访问限制,可以使用视图将它们挑出来然后仅将视图的权限给那些用户而不是整个表的权限
  • 修改视图:使用ALTER关键字修改一个已经创建的视图,重新指定被封装到其中的SELECT语句
    # 整个被封装到视图的select语句都需要重新指定
    ALTER VIEW view_name AS
    new_select_statement
    # 与Microsoft SQL Server和MySQL不同,Oracle在修改视图之前,需要使用DROP VIEW view_name先删除视图
    
  • 同样,修改视图与创建视图一样,只是修改了视图的定义,它本身不会返回任何数据
  • 删除视图:使用DROP VIEW view_name来删除视图
  • 包含在其他查询中的查询叫做子查询,子查询可以用在SELECTINSERTUPDATEDELETE语句
  • SELECT语句中子查询可以有三种用法:
  • 一个一般的SELECT语句格式如下:
    SELECT column_list
    FROM table_list
    WHERE condition
    GROUP BY column_list
    HAVING condition
    ORDER BY column_list
    
  • 当子查询是table_list的一部分时,它指定了一个数据源
  • 当子查询时condition的一部分时,它成为查询条件的一部分
  • 当子查询是column_list的一部分时,它创建了一个单个的计算的列
  • 使用子查询指定数据源:把一个子查询指定为FROM子句的一部分时,它立即创建了一个新的数据源,并被当做一个独立的表或视图来引用,与视图的区别是视图是永久保存在数据库中的,而子查询只是临时的
    # 使用子查询指定数据源的一般格式
    SELECT column_list
    FROM [table_list]
    [JOIN] subquery
    AS custom_subquery_name
    # 从address表,city表和country表中列出五个地址对应的城市和国家
    SELECT 
      address AS 'Address',
      city AS 'City',
      country AS 'Country'
    FROM address
    LEFT JOIN(
      SELECT 
          city.city,
          city.city_id,
          country.country,
          country.country_id
      FROM city
      LEFT JOIN country
      ON city.country_id = country.country_id
    ) AS city_and_country ON address.city_id = city_and_country.city_id
    ORDER BY address
    LIMIT 5
    
  • 使用子查询指定查询条件:把一个子查询指定为WHERE子句中IN操作符的右值,可以以更复杂的逻辑来为IN操作符创建一个可选列表;注意,当子查询用于指定查询条件时,仅能返回单一的列
    # 使用子查询指定查询条件的一般格式
    SELECT column_list
    FROM table_list
    WHERE column IN subquery
    SELECT column_list
    FROM table_list
    WHERE subquery match_some_comdition
    # 列出所有使用现金支付的客户名称
    SEELCT customer_name AS 'Customer Name'
    FROM costomers
    WHERE customer_id IN
      SELECT customer_id
      FROM orders
      WHERE order_type = 'cash'
    # 列出订单金额少于20美元的客户列表
    SELECT customer_name AS 'Customer Name'
    FROM customers
    WHERE
      SELECT SUM(orderAmount)
      FROM orders
      WHERE customers.customer_id = orders.customer_id
    
  • 使用子查询作为计算列:把一个子查询作为column_list中的一项,将其用作一个计算的列
    # 使用子查询作为计算列的一般格式
    SELECT column_list,
    subquery_result AS 'Result Alia'
    FROM table_list
    # 查询客户及其订单数量
    SELECT
    customer_name AS 'Customer Name',
      SELECT COUNT(order_id)
      FROM orders
      WHERE customers.customer_id = orders.customer_id
    ) AS 'Number of Orders'
    FROM customers
    ORDER BY customers.customer_id
    
  • 关联子查询:无法独立运行的子查询为关联子查询,可以独立运行的子查询为非关联子查询。非关联子查询完全独立与外围查询语句,只会计算和执行一次,而关联子查询需要针对返回的每一行逐行计算,且每次执行子查询的时候得到的结果可能都不一样,上文中查询客户及其订单数量中的子查询即为关联子查询,它使用了外围查询的数据来源customers
  • EXISTS操作符:用于确定一个关联子查询中是否存在数据
    # 查询下过订单的用户
    SELECT
    customer_name AS 'Customer'
    FROM customers
    WHERE EXISTS
      SELECT * FROM orders
      WHERE customers.customer_id = orders.customer_id
    

    在前文中,连接JOIN可以将来自两个表的列组合到一个表中,子查询则是将一条SELECT语句的结果提供给第另一条SELECT语句使用。然而有时候我们希望将来自两个表的行组合到一个表中,这时候就需要使用SQL中的集合逻辑UNION,来做合并查询。

    UNION-合并两条SELECT语句,选取在A或B中的数据,如果同时存在在A或B中,仅显示一条
    SELECT
    order_date AS 'Date',
    'order' AS 'Type',
    order_amount AS 'amount'
    FROM orders
    WHERE custom_id = 2
    UNION
    SELECT
    return_date AS 'Date',
    'return' AS 'type',
    return_amount AS 'amount'
    FROM returns
    WHERE custom_id = 2
    ORDER BY date
    使用UNION需要遵守3个规则(实际就一条规则:相同列):
    
  • 两个SELECT语句中的列的数量必须相等
  • 两个SELECT语句中的列排列顺序必须相同
  • 两个SELECT语句中的列数据类型必须相同
  • UNION ALL-合并两条SELECT语句,选取在A或B中的数据,即使同时存在在A或B中,都将显示在结果中
    SELECT
    DISTINCT
    order_date AS 'Date'
    FROM orders
    UNION ALL
    SELECT
    DISTINCT
    return_date AS 'Date'
    FROM returns
    ORDER BY Date
    # UNION 确保来自两个表的行没有重复数据,但 UNION ALL 允许来自两个表的行可以有相同数据
    # DISTINCT 确保来自同一个表(或者说同一个SELECT语句)的行没有重复数据
    # 所以上面的语句选取的数据可能会存在重复数据,但重复的数据并不来自两个表而是来自同一个表,并且仅会重复一次
    INTERSECT-合并两条SELECT语句,选取同时出现在A和B中的行(MySql不支持该操作符)
    
    SELECT order_date AS 'Date'
    FROM orders
    INTERSECT
    SELECT return_date As 'Date'
    FROM returns
    ORDER BY Date
    EXCEPT-合并两条SELECT语句,选取仅出现在A或仅出现在B中的的数据(MySql和Oracle不支持该操作符,但Oracle提供了等价的MINUS操作符)
    
    SELECT order_date AS 'Date' FROM orders
    EXCEPT
    SELECT return_date AS 'Date' FROM returns
    ORDER BY Date
    

    存储过程和参数

    到目前为止,前文所有的SQL语句都是单独使用,然而很多时候,你会希望SQL语句能够像函数一样,定义一次,然后重复调用,并且可以使用参数来增加灵活性。这时,你就可以使用存储过程来实现这一目的。

  • 创建存储过程:创建存储过程不会执行任何内容,只是直接创建了一个过程,以便后续执行它。与表和视图一样,创建好的存储过程在管理工具中是可以查看的
    -- Microsoft SQL Server
    CREATE PROCEDURE ProcedureName (OptionalPrameterDeclarations)
    BEGIN
    SQLStatements
    -- MySQL
    DELIMITER $$ -- 规定END语句使用的分隔符,默认为分号
    CREATE PROCEDURE ProcedureName (OptionalPrameterDeclarations)
    BEGIN
    SQLStatements; -- 每一条SQL语句都必须使用分号分隔,即使只有一条
    END$$
    DELIMITER ; -- 将分隔符改回分号
    
  • 存储过程的参数:例如存储一个选取特定用户的SQL过程,可以使用参数来指定用户的ID
    -- Microsoft SQL Server
    CREATE PROCUDURE CustomerProcudure (@custId INT)
    BEGIN
      SELECT * FROM customers
      WHERE customer_id = @custId
    -- MySQL
    DELIMITER $$
    CREATE PROCEDURE CustomerProcudure (custId INT)
    BEGIN
      SELECT * FROM customers
      WHERE CUSTOMER_ID = custId;
    DELEMITER ;
    
  • 执行存储过程
    -- Microsoft SQL Server
    EXEC CustomerProcudure @custId = 2
    -- MySQL
    CALL CustomerProcudure (2)
    
  • 修改和删除存储过程:在Microsoft SQL Server中,修改过程和创建过程几乎一样,只需要把CREATE关键字替换为ALTER关键字即可;然而在MySQL中,虽然也存在ALTER命令,但它的功能十分简单,所以一般我们选择先删除存储过程然后再重新创建
    -- 删除存储过程
    DROP PROCEDURE ProcedureName
    
  • 函数与存储过程的两点区别
  • 存储过程可以有任意数目的输出,而函数只有单一的返回值
  • 只能通过调用程序来执行存储过程,而函数可以在SQL语句中使用
  • 修改策略:使用“软删除(使用表中特定的列来标记该行数据是否有效)”技术替代真正的删除;插入新行时在特定列中标记准确的插入日期和时间以便出错时对其进行删除;使用单独的表来保存事务所要更新的数据通常是明智的选择。请永远记住,SQL中没有撤销命令。
  • 插入数据:使用INSERT命令来插入指定数据,注意不需要为自增型的列指定数据,数据库会自动处理它;另外,Oracle不允许一次插入多行数据,需要分开书写
  • 插入INSERT语句中指定的具体数据
    -- 向customer表插入两条新数据
    INSERT INTO customers
    (first_name, last_name, state) -- 只要列名是正确的,它们的顺序无所谓
    -- 当这里的列名顺序与数据库中的物理顺序一致时可省略它们,但强烈不建议这么做
    VALUES
    ('Virginia', 'Jones', 'OH'), -- VALUES关键字后的数据列,要与INSERT INTO后的列相对应
    ('Clark', 'Woodland', 'CA')
    
  • 插入用一条SELECT语句指定的数据
    -- 将customer_transaction中的RI州的用户插入到customer表中
    INSERT INTO customer
    (first_name, last_name, state)
    SELECT
    fn, ln, state -- 这里选中列的顺序需要与INSERT INTO 语句中的顺序一致
    FROM customer_transactions
    WHERE state = 'RI'
    -- 上面的语句与下面的DELETE语句效果基本相同
    DELETE FROM customers
    -- 唯一不同在于,TRUNCATE TABLE语句重置了自增列,而DELETE语句没有
    
  • 更新(修改)数据:删除数据只需要指定删除的行即可,但更新数据是针对具体行中的具体列,所以需要首先指定更新哪些列,然后指定更新这些列中的哪些行
  • 使用指定的具体数据更新数据
    -- 更新数据的一般格式
    UPDATE table
      column1 = expression1,
      column2 = expression2
      -- repeat any number of time
    WHERE conditions -- 如果没有指定行,该句会把所有行的指定列都更新一次
    
  • 使用子查询中的数据修改数据(使用一个表中的数据来更新另一个表中的数据)
    -- 一般格式
    UPDATE table -- 指定要更新的表
    SET table.column_1 = -- 指定需要更新的列1
        SELECT another_table.column_1 -- 子查询从另一表中获取数据,并通过主键(也可是其它)来进行匹配
        FROM another_table
        WHERE another_table.auto_increment_primary_key = table.auto_increment_primary_key
    SET table.column_2 = -- 指定需要更新的列2
        SELECT another_table.column_2
        FROM another_table
        WHERE another_table.auto_increment_primary_key = auto_increment_primary_key.column_2
    WHERE EXISTS -- 指定需要更新的行,使用子查询指定只更新table中存在于another_table中的行
        SELECT *
        FROM another_table
        WHERE another_table.auto_increment_primary_key = table.auto_increment_primary_key
    
  • 回顾SQL语言的三种组成部分:数据操纵语言(Data Manipulation Language,DML,对数据库中或者更详细地说是表中的数据进行增删改查操作)、数据定义语言(Data Definition Language,DDL,对数据库中的表及其索引进行增删改查)、和数据控制语言(Data Control Language,DCL,维护数据库安全)。本章主要讲述DDL,但前文也已经用到过DDL,视图VIEW、过程PROCEDURE需要用到的都是DDL
  • 添加或修改表和索引的SQL语句很复杂,但是我们无需了解细节,数据库软件通常提供了图形化的工具来修改表的结构,而不一定需要使用SQL语句
  • 表属性:表(Table)是数据库中最重要的对象,数据库中所有数据物理地存储在表中,没有了表数据库中的一切也就没有意义了。前文已经介绍过一些表的属性,主键、外键、数据类型、自增型列等等
  • 列名:表中的每个列都必须有唯一的列名
  • 数据类型:决定列可以包含什么样的数据
  • 是否自增型:表中每增加一行,该列会以升序序列自动赋值(术语auto-increment是MySQL中的的特定用法,Oracle没有自增型属性)
  • 主键和索引
  • 主键:只能指定一个列作为主键,目的是保证这个列包含唯一值(所以不允许它们包含NULL值);实际上主键可以跨越多个列,叫做复合主键(当希望使用电影名称列来作为主键时可能会存在重复名称, 这时可以使用电影名称+年份两个列作为复合主键来唯一地定义每部电影)
  • 索引:索引是一种物理结构,目的是当SQL语句中包含该列的时候,可以加速数据检索,缺点是需要更多的磁盘空间,并且会减慢更新数据时的速度
  • 外键定义:外键是从一个表中的一个列到另一个不同的表中的列的直接引用,含有外键的表为“子表”,被外键引用的表被称为“父表”
  • 外键级联(Cascade):当父表有更新或删除时,会自动更新或删除子表中的关联行
  • Set Null:当父表有更新或删除时,如果影响到子表,是否把子表中关联行的外键设置为NULL
  • 创建表:使用CREATE TABLE语句来创建表及其属性(列),不同数据库之间存在差异:
    -- Microsoft SQL Server
    CREATE TABLE my_table
      column_1 INT IDENTITY (1, 1) PRIMARY KEY NOT NULL, -- 列名column_1,INT类型,自增型,主键,不能为NULL
      column_2 NOT NULL REFERENCES related_table (first_column), -- 列名column_2,INT类型,不能为NULL,外键,关联到related_table表的first_column列
      column_3 VARCHAR (25) NULL, -- 列名column_3,VARCHART类型,可以是NULL
      column_4 FLOAT NULL DEFAULT (10) -- 列名column_4,FLOAT类型,可以是NULL
    -- My SQL
    CRAET TABLE my_table
      column_1 INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
      column_2 INT NOT NULL,
      column_3 VARCHAR (25) NULL,
      column_4 FLOAT NULL DEFAULT 10 NULL,
      CONSTRAINT FOREIGN KEY (column_2) REFERENCE 'related_table' (first_column) -- 指定外键
    -- Oracle
    CREATE TABLE my_table
      column_1 INT PRIMARY KEY NOT NULL, -- Oracle不允许有自增型的列
      column_2 INT NOT NULL,
      column_3 VARCHAR2 (25) null,
      column_4 FLOAT DEFAULT 10 NULL
      CONSTRAINT "foreign_key" FOREIGN KEY (column_2) REFERENCES related_table (first_column)
    使用ALTER TABLE语句修改表的具体属性,该语句的复杂性及数据库差异巨大,这里不再展开;使用DROP TABLE table_name语句来删除一个表
    
    -- 修改表
    ALTER TABLE my_table
    DROP COLUMN column_3
    -- 删除表
    DROP TABLE my_table
    
  • 创建索引:使用CREATE INDEX语句,用来在创建表之后创建索引,使用ALTER INDEX语句来添加和修改索引
    -- 创建索引
    CREATE INDEX index_2
    ON my_table (column_4)
    -- 删除索引
    DROP INDEX index_2
    ON my_table
    

    数据库设计原理与显示数据的策略(略)