【SQL 中级语法 2】自连接的用法

news2024/11/15 21:57:53

SQL的连接运算根据其特征的不同,有着不同的名称,如内连接、外连接、交叉连接等。一般来说,这些连接大都是以不同的表或视图为对象进行的,但针对相同的表或相同的视图的连接也并没有被禁止。针对相同的表进行的连接被称为“自连接”(self join)。一旦熟练掌握自连接技术,我们便能快速地解决很多问题。

理解自连接能增进我们对“面向集合”这一SQL语言重要特征的理解。面向对象语言以对象的方式来描述世界,而面向集合语言SQL以集合的方式来描述世界。自连接技术充分体现了SQL面向集合的特性。

目录

1 可重排列、排列、组合

2 删除重复行

3 查找局部不一致的列

4 排序

小结

____________________________________________________________________________

1 可重排列、排列、组合

Products

name(商品名称)  price(价格)
苹果            50
橘子            100
香蕉            80

这里所说的组合其实分为两种类型。一种是有顺序的有序对(ordered pair),另一种是无顺序的无序对(unordered pair)。有序对用尖括号括起来,如<1,2>;无序对用花括号括起来,如{1, 2}。在有序对里,如果元素顺序相反,那就是不同的对,因此<1, 2>≠<2, 1>;而无序对与顺序无关,因此{1, 2}={2, 1}。用学校里学到的术语来说,这两类分别对应着“排列”和“组合”。

用SQL生成有序对非常简单。像下面这样通过交叉连接生成笛卡儿积(直积),就可以得到有序对。

    --用于获取可重排列的SQL语句
    SELECT P1.name AS name_1, P2.name AS name_2
      FROM Products P1, Products P2;

执行结果

    name_1       name_2
    ------      ------
    苹果         苹果
    苹果         橘子
    苹果         香蕉
    橘子         苹果
    橘子         橘子
    橘子         香蕉
    香蕉         苹果
    香蕉         橘子
    香蕉         香蕉

执行结果里每一行(记录)都是一个有序对。因为是可重排列,所以结果行数为3的平方=9。结果里出现了(苹果,苹果)这种由相同元素构成的对,而且(橘子,苹果)和(苹果,橘子)这种只是调换了元素顺序的对也被当作不同的对了。这是因为,该查询在生成结果集合时会区分顺序。

如何更改才能排除掉由相同元素构成的对呢?首先,为了去掉(苹果,苹果)这种由相同元素构成的对,需要像下面这样加上一个条件,然后再进行连接运算。

    --用于获取排列的SQL语句
    SELECT P1.name AS name_1, P2.name AS name_2
      FROM Products P1, Products P2
     WHERE P1.name <> P2.name;

执行结果 

    name_1       name_2
    ------       ------
    苹果         橘子
    苹果         香蕉
    橘子         苹果
    橘子         香蕉
    香蕉         苹果
    香蕉         橘子

加上WHERE P1.name <> P2.name这个条件以后,就能排除掉由相同元素构成的对,结果行数为排列=6。

 ● P1里的“苹果”行的连接对象为P2里的“橘子、香蕉”这两行

● P1里的“橘子”行的连接对象为P2里的“苹果、香蕉”这两行

● P1里的“香蕉”行的连接对象为P2里的“苹果、橘子”这两行

相同的表的自连接和不同表间的普通连接并没有什么区别,自连接里的“自”这个词也没有太大的意义。

接下来对(苹果,橘子)和(橘子,苹果)这样只是调换了元素顺序的对进行去重。

    --用于获取组合的SQL语句
    SELECT P1.name AS name_1, P2.name AS name_2
      FROM Products P1, Products P2
     WHERE P1.name > P2.name;

执行结果

    name_1       name_2
    ------       ------
    苹果          橘子
    香蕉          橘子
    香蕉          苹果

在加上“不等于”这个条件后,这条SQL语句所做的是,按字符顺序排列各商品,只与“字符顺序比自己靠前”的商品进行配对,结果行数为组合C(2,3)=3。

想要获取3个以上元素的组合时,像下面这样简单地扩展一下就可以了。这次的样本数据只有3行,所以结果应该只有1行。

    --用于获取组合的SQL语句:扩展成3列
    SELECT P1.name AS name_1, P2.name AS name_2, P3.name AS name_3
      FROM Products P1, Products P2, Products P3
     WHERE P1.name > P2.name
      AND P2.name > P3.name;
    name_1    name_2     name_3
    -------   --------   --------
    香蕉       苹果        橘子

使用等号“=”以外的比较运算符,如“<、>、<>”进行的连接称为“非等值连接”。这里将非等值连接与自连接结合使用了,因此称为“非等值自连接”。在需要获取列的组合时,会经常使用。

“, >”和“<”等比较运算符不仅可以用于比较数值大小,也可以用于比较字符串(比如按字典序进行比较)或者日期等。

2 删除重复行

在关系数据库的世界里,重复行和NULL一样,都不受欢迎。

 重复行有多少行都没有关系。通常,如果重复的列里不包含主键,就可以用主键来处理,但像这道例题一样所有的列都重复的情况,则需要使用由数据库独自实现的行ID。这里的行ID可以理解成拥有“任何表都可以使用的主键”这种特征的虚拟列。在下面的SQL语句里,我们使用的是Oracle数据库里的rowid。

    --用于删除重复行的SQL语句(1):使用极值函数
    DELETE FROM Products P1
     WHERE rowid < ( SELECT MAX(P2.rowid)
                      FROM Products P2
                      WHERE P1.name = P2. name
                        AND P1.price = P2.price ) ;

(没理解)

 这里的重点也与前面的例题一样,对于在SQL语句里被赋予不同名称的集合,我们应该将其看作完全不同的集合。这个子查询会比较两个集合P1和P2,然后返回商品名称和价格都相同的行里最大的rowid所在的行。

于是,由于苹果和香蕉没有重复行,所以返回的行是“1:苹果”“5:香蕉”,而判断条件是不等号,所以该行不会被删除。而对于“橘子”这个商品,程序返回的行是“4:橘子”,那么rowid比4小的两行——“2:橘子”和“3:橘子”都会被删除。

(还是没理解)

通过这道例题我们明白,如果从物理表的层面来理解SQL语句,抽象度是非常低的。“表”“视图”这样的名称只反映了不同的存储方法,而存储方法并不会影响到SQL语句的执行和结果,因此无需有什么顾虑(在不考虑性能的前提下)。无论表还是视图,本质上都是集合——集合是SQL能处理的唯一的数据结构。

用前面介绍过的非等值连接的方法也可以写出与这里的执行过程一样的SQL语句。

    --用于删除重复行的SQL语句(2):使用非等值连接
    DELETE FROM Products P1
     WHERE EXISTS ( SELECT *
                      FROM Products P2
                    WHERE P1.name = P2.name
                      AND P1.price = P2.price
                      AND P1.rowid < P2.rowid );

3 查找局部不一致的列

 如果家庭ID一样,住址也必须一样。因此这里需要修改一下。那么我们该如何找出像前田夫妇这样的“是同一家人但住址却不同的记录”呢?

实现办法有几种,不过如果用非等值自连接来实现,代码会非常简洁。

    --用于查找是同一家人但住址却不同的记录的SQL语句
    SELECT DISTINCT A1.name, A1.address
      FROM Addresses A1, Addresses A2
     WHERE A1.family_id = A2.family_id
      AND A1.address <> A2.address ;

 和前面的住址表那道题的结构完全一样。家庭ID→价格;住址→商品名称

    --用于查找价格相等但商品名称不同的记录的SQL语句
    SELECT DISTINCT P1.name, P1.price
      FROM Products P1, Products P2
     WHERE P1.price = P2.price
      AND P1.name <> P2.name;

执行结果

    name       price
    ------    ------
    苹果          50
    葡萄          50
    草莓         100
    橘子         100
    香蕉         100

请注意,这里与住址表那道例题不同,如果不加上DISTINCT,结果里就会出现重复行。关键在于价格相同的记录的条数。而就住址表来说,如果前田家有孩子,那么不在代码中加上DISTINCT的话,结果里才会出现重复行。不过,这道例题使用的是连接查询,如果改用关联子查询,就不需要DISTINCT了。

4 排序

按照价格从高到低的顺序,对下面这张表里的商品进行排序。我们让价格相同的商品位次也一样,而紧接着它们的商品则有两种排序方法,一种是跳过之后的位次,另一种是不跳过之后的位次。

 首先想到的肯定是窗口函数

    --排序:使用窗口函数
    SELECT name, price,
          RANK() OVER (ORDER BY price DESC) AS rank_1,
          DENSE_RANK() OVER (ORDER BY price DESC) AS rank_2
      FROM Products;

执行结果

    name         price    rank_1    rank_2
    -------    ------   -------   -------
    橘子          100          1          1
    西瓜           80          2          2
    苹果           50          3          3
    香蕉           50          3          3
    葡萄           50          3          3
    柠檬           30          6          4

但是,RANK函数还属于标准SQL中较新的功能,目前只有个别数据库实现了它,还不能用于MySQL数据库。(这我不知道现在可不可以了)

考虑不依赖具体数据库,使用非等值自连接(真的很常用)来实现:

    --排序从1开始。如果已出现相同位次,则跳过之后的位次
    SELECT P1.name,
          P1.price,
          (SELECT COUNT(P2.price)
              FROM Products P2
            WHERE P2.price > P1.price) + 1 AS rank_1
      FROM Products P1
      ORDER BY rank_1;

执行结果

    name    price     rank
    -----   ------   ------
    橘子      100         1
    西瓜       80         2
    苹果       50         3
    葡萄       50         3
    香蕉       50         3
    柠檬       30         6

去掉标量子查询后边的+1,就可以从0开始给商品排序,如果修改成COUNT(DISTINCT P2.price),那么存在相同位次的记录时,就可以不跳过之后的位次,而是连续输出(相当于DENSE_RANK函数)。由此可知,这条SQL语句可以根据不同的需求灵活地进行扩展,实现不同的排序方式。(这也不理解)

这条SQL语句会生成这样几个“同心圆状的”递归集合,然后数这些集合的元素个数。正如“同心圆状”这个词的字面意思那样,这几个集合之间存在如下包含关系。

 实际上,“通过递归集合来定义数”这个想法并不算新颖。有趣的是,它和集合论里沿用了100多年的自然数(包含0)的递归定义(recursive definition)在思想上不谋而合。研究这种思想的学者形成了几个流派,其中和这道例题的思路类型相同的是计算机之父、数学家冯·诺依曼提出的想法。冯·诺依曼首先将空集定义为0,然后按照下面的规则定义了全体自然数。

    0 = φ
    1 = {0}
    2 = {0, 1}
    3 = {0, 1, 2}
        ·
        ·
        ·

定义完0之后,用0来定义1,然后用0和1来定义2,再用0、1和2来定义3……以此类推。这种做法与上面例题里的集合S0~S3在生成方法和结构上都是一样的(正是为了便于比较,例题里的位次才从0开始)。这道题很好地直接结合了SQL和集合论,而联系二者的正是自连接。

(鼓掌)

顺便说一下,这个子查询的代码还可以像下面这样按照自连接的写法来改写。

    --排序:使用自连接
    SELECT P1.name,
          MAX(P1.price) AS price,
          COUNT(P2.name) +1 AS rank_1
      FROM Products P1 LEFT OUTER JOIN Products P2
        ON P1.price < P2.price
     GROUP BY P1.name
     ORDER BY rank_1;

(啊我没看懂!)

去掉这条SQL语句里的聚合并展开成下面这样,就可以更清楚地看出同心圆状的包含关系(为了看得更清楚,我们从表中去掉价格重复的行,只留下橘子、西瓜、葡萄和柠檬这4行)。

    --不聚合,查看集合的包含关系
    SELECT P1.name, P2.name
      FROM Products P1 LEFT OUTER JOIN Products P2
        ON P1.price < P2.price;

 集合每增大1个,元素也增多1个,通过数集合里元素的个数就可以算出位次。

这个查询里还有一个特别的技巧,也许大家已经注意到了。那就是前面的例题里用的连接都是标准的内连接,而这里用的却是外连接。如果将外连接改为内连接看一看,马上就会明白这样做的原因。

    --排序:改为内连接
    SELECT P1.name,
          MAX(P1.price) AS price,
          COUNT(P2.name) +1 AS rank_1
      FROM Products P1 INNER JOIN Products P2
        ON P1.price < P2.price
     GROUP BY P1.name
     ORDER BY rank_1;

执行结果

    --没有第1名!
    name     price     rank_1
    ------   -------   --------
    西瓜       80          2
    葡萄       50          3
    苹果       50          3
    香蕉       50          3
    柠檬       30          6

没错,第1名“橘子”竟然从结果里消失了。关于这一点大家思考一下就能理解了。没有比橘子价格更高的水果,所以它被连接条件P1.price < P2.price排除掉了。外连接就是这样一个用于将第1名也存储在结果里的小技巧(这个小技巧在1-6节还会再次发挥重要作用)。

(啊,我还是没有完全理解,感觉会又不太会!原来sql可以这名难)

小结

自连接是不亚于CASE表达式的重要技术,请一定熟练掌握。最后说一个需要注意的地方,与多表之间进行的普通连接相比,自连接的性能开销更大(特别是与非等值连接结合使用的时候),因此用于自连接的列推荐使用主键或者在相关列上建立索引。本节例题里出现的连接大多使用的是主键。

1.自连接经常和非等值连接结合起来使用。

2.自连接和GROUP BY结合使用可以生成递归集合。

3.将自连接看作不同表之间的连接更容易理解。

4.应把表看作行的集合,用面向集合的方法来思考。

5.自连接的性能开销更大,应尽量给用于连接的列建立索引。

———————————————————————————————————————————

更多内容点击主页查看~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/12395.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python之排序

目录1. 对一维array中的数值进行从大到小排序2. 将DataFrame的列逆序排列3. 根据字符串中的数字进行排序3.2 啊4. 列表参考资料1. 对一维array中的数值进行从大到小排序 import numpy as np a np.array([5,6,8,2,1,7,5,3,90,78,62,5,4,2,9,4]) # b a.sort(axis0,kindquickso…

抑制剂拮抗剂等小分子化合物

小分子化合物主要通过调节其蛋白靶点的活性发挥作用。目前小分子化合物的蛋白靶点主要包括酶、离子通道和受体三大类。根据靶点种类的不同&#xff0c;小分子化合物发挥着不同的作用。 1. 酶的抑制剂&#xff08;enzyme inhibitor&#xff09; 在所有的小分子化合物中&#xff…

【虹科案例】虹科脉冲发生器在半导体行业中的应用

非易失性存储单元特点 存储器研究的趋势是开发一种称为非易失性 RAM 的新型存储器&#xff0c;它将 RAM 的速度与大容量存储器的数据存储相结合。几年来有许多新单元类型的提议&#xff0c;例如 FeRAM&#xff08;铁电存储器&#xff09;、ReRAM&#xff08;电阻式存储器&…

C++string类的模拟实现以及经验分享

文章目录1. 为什么学习string类&#xff1f;1.1 C语言中的字符串1.2 两个面试题2. string类的实现构造函数&#xff1a;拷贝构造函数赋值运算符重载&#xff1a;析构函数流提取运算符重载1. 为什么学习string类&#xff1f; 1.1 C语言中的字符串 C语言中&#xff0c;字符串是…

5款十分小众,却又非常好用的良心软件

今天推荐5款十分小众的软件&#xff0c;知道的人不多&#xff0c;但是每个都是非常非常好用的&#xff0c;有兴趣的小伙伴可以自行搜索下载。 1.杀毒软件——火绒安全软件 首先说一下国产杀软之光&#xff0c;这是一款电脑安全软件&#xff0c;病毒库更新及时&#xff0c;界面…

【文档+视频】Verdi基础教程

目录 前言 1.Verdi 环境配置 2.VCS 产生Verdi 波形 1、tb中加入相应的系统函数 2、makefile中加入相应的选项 3.nTrace 1、如何调用Verdi&#xff1f; 【重点】 2、如何查看包含的设计架构&#xff1f; 3、如何查寻模块实例化的位置&#xff1f;【重点】 4、在nTrace…

Linux网络编程11——简单的web服务器

学习视频链接 02-web大练习的概述_bilibili_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA/?p132&spm_id_frompageDriver&vd_source0471cde1c644648fafd07b54e303c905 目录 一、项目展示 二、HTTP 协议基础 2.1 HTTP协议基础。 2.2 请求消息(R…

个人博客系统

目录一、项目简介二、项目开发流程2.1 准备工作2.2 实现Vue层2.2 实现Model层2.4 实现Controller层一、项目简介 基于servlet&#xff0c;采用前后端分离的方式&#xff0c;实现个人博客系统&#xff0c;功能包括&#xff1a;登录、注销、编辑、删除博客、发布博客等. 二、项…

用了10年开源工具,换了Smartbi后,3分钟搞定一份报表

大约在20年前&#xff0c;中国企业开始应用国外BI软件&#xff0c;报表工具可以说是BI 1.0时代的代表产物。在BI软件盛行之初&#xff0c;大部分软件都有开源的&#xff0c;从系统到数据库到各类工具、应用&#xff0c;当时大部分企业使用的BI软件包括报表工具&#xff0c;也都…

外汇天眼:外汇占款是什么意思? 与外汇储备之间的差额是由哪些原因造成的?

外汇占款就是指受资国中央银行回收外汇财产而相对投放的本币。 因为人民币是是非非随意换取代币&#xff0c;外资企业导入后需换取成人民币才可以进到商品流通应用&#xff0c;国家以便外资企业兑换外币要投入很多的资产提升了贷币的需要量&#xff0c;产生了外汇占款。 外汇占…

线程池源码解析 3.excute() 方法

线程池源码解析—excute()方法 execute() execute 方法是线程池的核心方法&#xff0c;所有的方法&#xff0c;包括包装的 FutureTask&#xff0c;都是调用这个方法。 大致流程 这里只是总结了一遍大致的流程&#xff0c;一些细节问题见下面的流程图或者参考源码。 当提交任…

【毕业设计】大数据电商销售预测分析 - python 数据分析

文章目录1 前言2 开始分析2.1 数据特征2.2 各项投入与销售额之间的关系2.3 建立销售额的预测模型3 最后1 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#x1f525; 对毕设有任何疑问都可以问学长哦! 这两年开始&#x…

Java8新特性 CompletableFuture

Java8新特性 CompletableFuture 什么是CompletableFuture&#xff1f; CompletableFuture类的设计灵感来自于 Google Guava 的 ListenableFuture 类&#xff0c;它实现了 Future 和 CompletionStage 接口并且新增了许多方法&#xff0c;它支持 lambda表达式&#xff0c;通过回…

【IDEA插件】这5款IDEA插件,堪称代码BUG检查神器!

随着业务的发展&#xff0c;系统会越来越庞大&#xff0c;原本简单稳定的功能&#xff0c;可能在不断迭代后复杂度上升&#xff0c;潜在的风险也随之暴露&#xff0c;导致最终服务不稳定&#xff0c;造成业务价值的损失。而为了减少这种情况&#xff0c;其中一种比较好的方式就…

5.盒子阴影(重点)

提示&#xff1a;css3中新增了盒子阴影&#xff0c;我们可以使用box-shadow属性为盒子添加阴影。 1、语法&#xff1a; div{ box-shadow:"h-shadow"或者“v-shadow” } 解释&#xff1a; h-shadow 必须&#xff0c;水平阴影位置&#xff0c;允许负值。 v-shado…

UE4 回合游戏项目 18- 退出战斗

在上一篇&#xff08;UE4 回合游戏项目 17- 进入指定区域触发战斗事件&#xff09;基础上完成击败敌人从而退出战斗的功能。 效果&#xff1a; 步骤&#xff1a; 1.打开“battleScenario”蓝图&#xff0c;添加一个自定义事件&#xff0c;命名为“离开战斗” ​ 2.删除所有…

[附源码]Python计算机毕业设计_社区无接触快递栈

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

手撕二叉搜索树

目录 一、概念 二、常见操作 2.1 查找操作 2.2 插入操作 2.3 删除操作 三、模型应用 3.1 K模型 3.2 KV模型 3.3 代码完整实现 四、 性能分析 一、概念 二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树 它或者是一棵空树&#xff0c;或者是具有以下…

Spring整合Mybatis和Junit小案例(9)

Spring整合Mybatis和Junit环境准备步骤1&#xff1a;准备数据库步骤2&#xff1a;创建项目导入jar包步骤3&#xff1a;根据数据库的表创建模型类步骤4&#xff1a;创建Dao接口步骤5&#xff1a;创建Service接口和实现类步骤6&#xff1a;添加jdbc.properties文件步骤7&#xff…

5种常用格式的数据输出,手把手教你用Pandas实现

导读:任何原始格式的数据载入DataFrame后,都可以使用类似DataFrame.to_csv()的方法输出到相应格式的文件或者目标系统里。本文将介绍一些常用的数据输出目标格式。 01 CSV DataFrame.to_csv方法可以将DataFrame导出为CSV格式的文件,需要传入一个CSV文件名。 df.to_csv(done.…