自连接的用法

本文介绍如何使用SQL去除重复记录,实现商品的不同组合,并通过多种方法进行数据排序,包括使用子查询、自连接及开窗函数等高级技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可重排列、排列、组合

假设现在有这样一张表:

create table products
(
name varchar(10),
price int
)character set=utf8
;

insert into products values
('苹果',50),
('橘子',100),
('香蕉',80);

现在需要把该表中不同的商品进行组合,同一种组合只能出现一次。单纯的使用笛卡尔积可以得到全排列组合,里面会有(苹果,苹果)这样的相同元素组合,或者是(苹果,橘子),(橘子,苹果)这样的组合相同但是顺序不同的元素组合。
为去除相同元素的组合,可以在限制条件中加上限制条件来去除相同元素。

SELECT P1.name AS name_1, P2.name AS name_2
  FROM products P1, products P2
 WHERE P1.name <> P2.name;

在这里插入图片描述
这样得到的结果就会去除掉相同元素的集合。更进一步的,如果要去除结果中的元素相同但顺序不同的数据,这时可以通过如下写法来完成。

SELECT P1.name AS name_1, P2.name AS name_2   
FROM products P1, products P2  
WHERE P1.name > P2.name;

在这里插入图片描述
这样得出的结果就都是完全唯一的了。
如果推广到两个及以上的组合则可以按照如下写法,以 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;

在这里插入图片描述
在获取列的组合时,这种非等值自连接的做法非常常见,需要牢记。

删除重复行

删除重复行的方法很多,有些可以直接删,有些需要配合主键才能删,下面分别介绍一下。
例如表中现在有以下数据。
在这里插入图片描述
在有主键或唯一标识的情况下,可以通过如下写法来进行去重。

delete from products p1
where id>(select max(p2.id) from products p2 
          where p1.name=p2.name
          and p1.price=p2.price)

这里使用的是关联子查询,主查询中的每行数据都会到子查询中再查一次。例如第一行数据(苹果,50)进入到子查询中得到的最大 id 是 6 ,1<6 所以第一行数据就会被删除。按此逻辑上述条件只要不写等于都能实现删除重复行的功能。
此外上述语句也可以换一种写法使用 exists 来实现。

delete from products p1
where exists(select * from products p2 
            where p1.name=p2.name
            and p1.price=p2.price
            and p1.id>p2.id)

唯一标识也可以数据库内置的虚拟列,例如 oracle 的 rowid 和 postgresql 的 oid。
对于没有唯一键的表,可以通过如下写法来实现。

 create table products_copy as 
 select distinct name,price 
 from products;

这样可以将去重后的数据存入另一张表中,后续再覆盖掉原表就完成了去重操作。

查找局部不一致的列

在这里插入图片描述
如图所示表,当需要找出属于同一家庭但地址不同的条目时,可以使用如下写法。

SELECT  A1.name, A1.address
FROM Addresses A1, Addresses A2
WHERE A1.family_id = A2.family_id
AND A1.address <> A2.address ;

这样就可以得出如下结果:
在这里插入图片描述
这种写法可以得出一张表中局部一致的数据,但是但局部相同的数据量大于 2 条时,得出的数据会存在重复值,这时需要使用 distinct 来进行去重。
例如如下表,我们现在需要查找出价格相同但名称不同的条目.
在这里插入图片描述
如果使用之前的逻辑,可以写出如下 SQL。

SELECT  P1.name, P1.price
  FROM products P1, products P2
 WHERE P1.price = P2.price
   AND P1.name <> P2.name;

从 sql 运行结果可以看出其中存在重复数据。
在这里插入图片描述
这种时候就需要在前面加上 distinct 来进行去重。

SELECT  distinct P1.name, P1.price
  FROM products P1, products P2
 WHERE P1.price = P2.price
   AND P1.name <> P2.name;

在这里插入图片描述
如果使用关联子查询可以不使用 distinct,写法如下:

SELECT  P1.name, P1.price
  FROM products P1
 WHERE exists (select * from products P2
   where P1.price = P2.price
   and P1.name <> P2.name
   );

在这里插入图片描述
但如果表中本身就存在重复数据,那么使用子查询也会有重复数据。

排序

现在需要对如下表的数据按照价格进行排序,现在有两种排序方法,一种是跳过并列的排名一种是不跳过并列的排名。
在这里插入图片描述
对于这种需求一般情况下我们直接通过开窗函数就能直接实现。

SELECT name, price,
       RANK() OVER (ORDER BY price DESC) AS rank_1,
       DENSE_RANK() OVER (ORDER BY price DESC) AS rank_2
  FROM products;

不过目前并不是所有数据库都实现了该函数,例如 MySQL 中目前还无法使用该函数,所以在 MySQL 中就需要使用其他的方法。

select P1.name,P1.price,
(select count(P2.price) from products P2
where P2.price>P1.price)+1 rank_1,
(select count(distinct P3.price) from products P3
where P3.price>P1.price)+1 rank_2
from products P1
order by rank_1;

在这里插入图片描述

这里需要重提一下子查询的知识点,主查询中的每一条数据都会执行一次子查询。例如对上面的 rank_1,主程序读到第一个数据苹果,然后会将其带入子查询中,得出有 4 个比苹果贵的水果,然后计算出名次为5,以此类推。
该子查询也可更改为自连接的形式

select P1.name,max(P1.price)price,
count(P2.price)+1 rank_1,
count(distinct P2.price)+1 rank_2
from products P1 left join products P2
on P1.price<P2.price
group by P1.name
order by rank_1;

在这里插入图片描述
注意该处使用的是左关联而不是内连接,如果使用内连接的话最贵的水果因为没有比它更贵的水果,那么在 P1.price<P2.price 限制条件下就会丢掉第一名的结果,示例如下:
在这里插入图片描述

练习

对于如下表,按地区进行排序
在这里插入图片描述
有两种写法,分别为子查询写法与自连接写法,窗口函数 mysql 无法实现就不写了

--子查询写法
select P1.district,P1.name,P1.price,
(select count(P2.price) from DistrictProducts P2
where P1.district=P2.district
and P1.price<P2.price)+1 rank_1
from DistrictProducts P1
order by P1.district,P1.name;

--自连接写法
select P1.district,P1.name,max(P1.price) price,
count(P2.price)+1 rank_1
from DistrictProducts P1 left join DistrictProducts P2
on P1.district=P2.district and P1.price<P2.price
group by P1.district,P1.name
order by P1.district,P1.name;

以上两种写法得到的结果都是一致的。
在这里插入图片描述
如果现在在表 DistrictProducts 后新加一列 ranking,该列初始值为 null,然后需要动态的来更新该列,则可以按照如下写法。

update DistrictProducts P1
set ranking=(select count(P2.price)+1 from DistrictProducts P2
where P1.district=P2.district
and P1.price<P2.price);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值