关系型数据库和SQL
基本数据检索
SELECT * FROM tablename
SELECT columnname FROM tablename
SELECT column1, column2 FROM tablename
带空格的列名
:Microsoft SQL Server使用方括号
[]
,MySQL使用重音符`,Oracle使用双引号
计算字段和别名
SELECT QuantityPurchased * PricePerItem FROM Orders
+
,MySQL使用
CONCAT()
函数连接字符串,Oracle使用双竖线
||
SELECT f_n AS 'first_name' from customers
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
关键字之后
SELECT
和ORDER BY
后面指定的列,可能是完全不同的一些列
使用关键字ASC
和DESC
来升序/降序排列
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
子句总是在FROM
和ORDER 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'
还有两个表示范围的操作符BETWEEN
和IN
,用于替代column >= range_bottom AND column <= range_top
和column = 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 NULL
在WHERE
子句中判断一个列是否为空,也可以与函数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
在没有分组的情况下,聚合函数(SUM
、AVG
、MIN
、MAX
、COUNT
)统计的是所有行的数据,在有分组的情况下,这些聚合函数则仅会统计组内的数据,当然实际上也是最终显示的表的每一行的聚合
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
内连接只会返回关联的两个表之间相匹配的数据,表在FROM
和INNER JOIN
之间的顺序仅会影响哪个表的列先显示,不会影响行的顺序
SQL不是过程式语言,不会指定任务的先后顺序,而只是指定需要的逻辑并让数据库内部机制去决定如何执行任务。
仅使用FROM
和WHERE
也可以指定表的内连接,这是内连接的另一种范式,但因其没有显示地表示出连接的逻辑,所以不推荐使用(所以其实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 NULL
和IS 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
来删除视图
包含在其他查询中的查询叫做子查询,子查询可以用在SELECT
、INSERT
、UPDATE
、DELETE
语句
在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
数据库设计原理与显示数据的策略(略)