SQL 与关系代数
Table of Contents
前言
SQL 是基于关系代数的查询语言,假如学习过 SQL 和关系代数,你就会发现,在 SQL 的查询语句中你会发现很多关系代数的影子。
然而,虽然知道 SQL 和关系代数密切相关,但是我也不知道学了关系代数对学习 SQL 有什么好处,因此,如果你对关系代数没兴趣的话,现在就可以关掉这篇博客了。
关系与表
我们可以把数据库中的表和关系代数中的关系看做是一个东西,这里给出接下来会用到的两个关系(表):
User
+------+---------+--------+
| id | account | passwd |
+------+---------+--------+
| 1 | 123 | ****** |
| 2 | 456 | ****** |
+------+---------+--------+
Profile
+------+------+------+
| id | name | age |
+------+------+------+
| 1 | tony | 16 |
| 3 | john | 2 |
+------+------+------+
关系代数的基本运算
关系代数的基本运算包括:选择、投影、并、集合差、笛卡尔积和更名。
投影
这里我们可以先来看一看 投影 运算,它的作用和 SQL 中的 SELECT
基本相同。
比如说我们要选择 User
中的 account
, 用 SQL 编写的话就是:
SELECT account FROM user;
如果用关系代数来写的话,就可以写成 \(\prod_{account}(user)\).
选择多列就可以这样: \(\prod_{id,account}(user)\).
选择
由于一些历史原因,关系代数中的选择和 SQL 中的 SELECT
不是一个意思,而是更接近 WHERE
, 我们可以通过选择运算选择关系中符合指定条件的部分。
比如说 \(\sigma_{id=1}(user)\) 可以选择关系 User
中 id
等于 1
的用户,其等价的 SQL 语句如下:
SELECT * FROM user WHERE id = 1;
选择运算中可以使用的谓词包括: \(=, \neq, <, \leqslant, >, \geqslant\). 同时还可以使用连词 \(and(\land), or(\lor), not(\lnot)\) 将多个谓词合并为一个较大的连词。
比如说 \(\sigma_{id \geqslant 1 \land id < 3}\) 选择 id
范围在 [1, 3)
之间的用户,等价于:
SELECT * FROM user WHERE id >= 1 AND id < 3;
同时,由于关系运算的结果依然是一个关系,因此,我们可以将关系运算组合起来,比如:选择 id 为一的用户的 account 可以表示为 \(\prod_{account}(\sigma_{id=1}(user))\)
并运算
并运算可以将两个集合并起来,对应到 SQL 中就是 UNION
操作,比如说获取 User 和 Profile 中的所有 ID:
SELECT id FROM user UNION SELECT id FROM profile;
用关系代数来表示的话就是: \(\prod_{id}(user) \cup \prod_{id}(profile)\).
关系代数的并运算和 SQL 中的 UNION
一样,要求需要并起来的关系的 列 是相同的,同时,比 SQL 更严格的是,关系代数的并运算还要求列的位置相同。
集合差运算
集合差运算可以从一个集合中排除另一个集合中的内容,对于到 SQL 中就是 EXCEPT
操作,比如获取 User 不在 Profile 中的所有 ID1:
SELECT id FROM user EXCEPT SELECT id FROM profile;
用关系代数来表示的话就是: \(\prod_{id}(user) - \prod_{id}(profile)\).
集合差运算对不同关系的要求和并运算是相同的。
笛卡尔积
笛卡尔积是一个很重要的运算,通过笛卡尔积我们可以将 任意 两个关系的信息结合在一起,笛卡尔积的运算结果会将两个关系的所有列作为新的关系的列,将两个关系的所有行的组合作为新的关系的行。
对应到 SQL 中便是 CROSS JOIN
, 比如说如下 SQL 语句便可以表示为 \(user \times profile\):
SELECT * FROM user CROSS JOIN profile;
运算结果如下:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
| 2 | 456 | ****** | 1 | tony | 16 |
| 1 | 123 | ****** | 3 | john | 2 |
| 2 | 456 | ****** | 3 | john | 2 |
+------+---------+--------+------+------+------+
更名运算
关系代数中的更名运算对应到 SQL 中便等价于 AS
操作,可以对关系进行更名也可以对列进行更名操作:
- 更名关系 - \(\rho_{users}(user)\)
- 更名列 - \(\rho_{users(uid,account,password)}(user)\)
在进行连接操作的时候常常会用到更名操作,而 SQL 中的更名操作用起来比关系代数中的方便一些,形象一些。
关系代数的附加运算
关系代数的附加运算是可以通过基本运算推导得出的,包括集合交运算和各类连接运算。
集合交运算
集合交运算计算两个关系中都存在的部分,可以通过基本运算表示: \(r \cap s = r - (r - s)\).
集合交运算对于的 SQL 语句是 INTERSECT
, 比如:
SELECT id FROM user INTERSECT SELECT id FROM profile;
表示为关系代数便是 \(\prod_{id}(user) \cap \prod_{id}(profile)\).
连接运算
个人认为连接运算是所有运算中最难的一种,它存在很多分类,比如:自然连接、内连接、外连接等。
同时,不同的连接运算之间还存在不浅的关系,因此,需要好好理解才行。
自然连接
首先是自然连接,自然连接将两个关系的 属性集 的 并集 作为新的关系的属性,同时会对两个关系中的相同属性进行比较筛选。
假如两个关系不存在相同的属性,那么自然连接的结果便和 笛卡尔积 相同:
+------+---------+--------+------+------+
| id | account | passwd | name | age |
+------+---------+--------+------+------+
| 1 | 123 | ****** | tony | 16 |
+------+---------+--------+------+------+
如上便是 自然连接 的运算结果,它将关系 User 和 Profile 的属性的并集作为新关系的属性,同时筛选具有相同 ID 值的行。
连接运算的关系代数形式都很复杂,这里就简单列出对应的 SQL 语句好了2:
SELECT * FROM user NATURAL JOIN profile;
内连接
可以把内连接3 看做添加了选择语句的笛卡尔积,也就是说,计算内连接时需要先行计算出笛卡尔积,然后在根据选择条件进行选择。
比如这样的内连接操作:
SELECT * FROM user INNER JOIN profile ON user.id >= profile.id;
其结果为:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
| 2 | 456 | ****** | 1 | tony | 16 |
+------+---------+--------+------+------+------+
这里可以对照笛卡尔积的计算结果进行理解:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
| 2 | 456 | ****** | 1 | tony | 16 |
| 1 | 123 | ****** | 3 | john | 2 |
| 2 | 456 | ****** | 3 | john | 2 |
+------+---------+--------+------+------+------+
外连接
我们可以把外连接看做是 内连接 的扩展4,首先计算出两个关系内连接的结果,然后根据外连接的类型补充数据到内连接的结果上。
比如说左外连接,首先可以计算出 User 和 Profile 的内连接,然后用空值来填充在左侧关系中存在而右侧关系中不存在的项就可以了。
SELECT * FROM user LEFT JOIN profile on user.id = profile.id;
这条 SQL 语句的执行结果为:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
| 2 | 456 | ****** | NULL | NULL | NULL |
+------+---------+--------+------+------+------+
如果将其替换为内连接的话便是:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
+------+---------+--------+------+------+------+
可以看到,ID 为 2 的项只存在于 User 中而不存在与 Profile 中,因此,左外连接时使用了空值来填充 Profile 对应的部分,保证 User 的每项都存在。
依次类推,右外连接、全外连接也就好理解了:
右外连接的执行结果:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
| NULL | NULL | NULL | 3 | john | 2 |
+------+---------+--------+------+------+------+
全外连接的执行结果:
+------+---------+--------+------+------+------+
| id | account | passwd | id | name | age |
+------+---------+--------+------+------+------+
| 1 | 123 | ****** | 1 | tony | 16 |
| 2 | 456 | ****** | NULL | NULL | NULL |
| NULL | NULL | NULL | 3 | john | 2 |
+------+---------+--------+------+------+------+
其实,这三个外连接是可以互相转换的,将两个关系的位置换一下就可以将左外连接转换为右外连接,而将左右外连接的结果并起来就可以得到全外连接了。
结语
虽然说关系代数和 SQL 有不浅的关系,但是学了关系代数,对编写 SQL 也没有多大的帮助 @_@
而且,不同的数据库实现 SQL 的语法还存在细微的差别……
也许,可以借助关系代数表达式来生成 SQL 语句!
其实关系代数还有一些扩展运算,对应到 SQL 中便是聚集、分组之类的,博客中没有说到,有兴趣的话可以去了解一下。
或者什么时候有时间了补上 @_@
Footnotes
1 不同数据库对 EXCEPT
子句的支持存在区别,这里的 SQL 语句不一定能运行通过
3 其实在关系代数中内连接应该叫做 theta 连接, 这里主要是为了和 SQL 相对应
4 其实按照书《数据库系统概念》中的描述的话应该是 自然连接, 但是实际的操作结果更符合 内连接, 虽然说,内连接也可以看做是自然连接
SQL 与关系代数的更多相关文章
- sql是最成功的第四代语言
SQL发展的前世今生 很多年前,两名年轻的IBM研究员将一门关系型语言带到了数据库领域,旨在使用声明性的方式来操作数据.从Don Chamberlin和Ramond Boyce发表"SEQU ...
- 【翻译】Flink Table Api & SQL —Streaming 概念 ——动态表
本文翻译自官网:Flink Table Api & SQL 动态表 https://ci.apache.org/projects/flink/flink-docs-release-1.9/de ...
- DDD:建模原语 之 四象图(转载的神文)
“模型.状态和行为特征.场景”和“四象图”,建模观的命名与立象. 建模原语:四象图 作者:achieveidea@gmail.com 命名:模型.结构特征.行为特征.场景(及其规约). 释义:模型,描 ...
- 数据模型与查询语言 ------《Designing Data-Intensive Applications》读书笔记2
数据模型是开发软件的最重要的部分,因为它们对应用程序有着深远的影响:不仅是软件的编写方式,而且也影响我们如何解决的问题的方式.第二篇读书笔记,我们聊一聊数据模型的设计. 1.数据模型的分层 作为一个开 ...
- 【转载】【Todo】银弹与我们的职业
看到一段文字,不得不单独拎出来. 然后再借用一下g9老大的<银弹和我们的职业>中的话: 银弹和我们的职业发展有什么相干?很简单:我们得把时间用于学习解决本质困难.新技术给高手带来方便.菜鸟 ...
- 春蔚专访--MaxCompute 与 Calcite 的技术和故事
摘要:2019大数据技术公开课第一季<技术人生专访>,来自阿里云计算平台事业部高级开发工程师雷春蔚向大家讲述了MaxCompute 与 Calcite 的技术和故事. 具体内容包括: 1) ...
- 第10讲:利用SQL语言实现关系代数操作
一.实现并.交.差运算 1. 基本语法形式:子查询 [union [all] | intersect [all] | except [all] 子查询] ①意义:将关系代数中的∪.∩.- 分别用uni ...
- 数据库系统概论学习3-SQL 语句和关系代数(一)SQL 入门
3. SQL 语句和关系代数(一)SQL 入门 3.1 数据库的编程语言 SQL 的优点 SQL 集成了数据查询(data query).数据操作(data manipulation).数据定义(da ...
- 关系代数(Relation Algebra)与SQL语句的对应关系
SQL语句的执行一般是先翻译为关系代数再被执行的(能有效提高执行速度),所以我们有必要 了解关系代数与SQL语句间的对应关系. 就像高中代数由+-*/和数字组成,关系代数是由union.interse ...
随机推荐
- 解决Zend加密的PHP页面出现Incompatible file format的问题
错误如图: 致命错误:不兼容的文件格式可能的原因: 1.文件本身加密的问题,很有可能你用的Zend进行加密了,但是因为版本的问题,很有可能是你的主机上的Zend Optimizer版本太低了.2.文件 ...
- IOS item属性总结
一.UINavigationItem1> 获得方式self.navigationItem // self是指控制器 2> 作用可以用来设置当前控制器顶部导航栏的内容// 设置导航栏中间的内 ...
- IOS @property的参数说明
1. @property的参数说明========================================ARC是苹果为了简化程序员对内存的管理,推出的一套内存管理机制使用ARC机制,对象的申 ...
- mysql的慢查询实战+sql优化
背景:使用A电脑安装mysql,B电脑通过xshell方式连接,数据内容我都已经创建好,现在我已正常的进入到mysql中 步骤1:设置慢查询日志的超时时间,先查看日志存放路径查询慢日志的地址,因为有慢 ...
- Edmonds-Karp算法,最大流POJ(1459)
题目链接:http://poj.org/problem?id=1459 解题报告: 电力调度站不涉及流的产生和消耗,不用考虑,Edmonds-Karp算法,就是利用剩余网络和增广路来解决,网络中的最大 ...
- 问题 B: C++习题 对象数组输入与输出
题目描述 建立一个对象数组,内放n(n<10)个学生的数据(学号.成绩),用指针指向数组首元素,输出第奇数(1,3,5,7)个学生的数据. 输入 n和n个学生的学号.成绩 输出 奇数学生的数据 ...
- CUDA memory
原文链接 CUDA存储器类型: 每个线程拥有自己的register and loacal memory; 每个线程块拥有一块shared memory; 所有线程都可以访问global memory; ...
- XML 对xml文件的crud的增加 create操作 增加元素 增加属性
把创建的节点挂到上一节点的最后 找到参考节点,使用insertBefore方法进行插入位置 xml添加属性使用setAttribute方法
- ios统计流量代码
#include <ifaddrs.h> #include <sys/socket.h> #include <net/if.h> 1.3G/GPRS流量统计 int ...
- (排班表三)导出列名不固定的Grid表格到Excel
将班表信息导出Excel表格保存到本地 要求:文档名称为[XXXX]年X月值班表 文档显示的效果: 实现代码: //导出Excel值班表 private void btn_export_1_Click ...