如何找到不是顾客购买因素的因素?SQL递归问题之生成递归的三种解法

在这里插入图片描述
SQL递归是数据无中生有或是父子关系这一类问题的特定解法。我们经常会遇到要找出不在某张表里的数据,但是这张表里的数据是缺失或者只有部分信息,那么就需要自己造数据;也会遇到部门上下级的编号在同一列需要找出谁是老板,谁是经理,谁是员工这样的问题。

初学者如果没有学过解决这类问题的办法,可能会束手无策。尝试连接查询、联合查询、窗口函数、条件筛选等等方法可能都找不到解法。下面用实际案例数据还原真实取数场景,帮助你在实战中理解如何实现SQL递归取数的过程,总结思路和规律。

需求:需要你写一个SQL语句找出在FactInternetSalesReason表中不在DimSalesReason表中的SalesReasonkey,你该怎么做?

背景:数据来源于微软示例数据库,一家销售自行车制造公司的销售数据,公司为了改善产品,增进用户体验,需要了解顾客购买产品的消费理由,现在公司收集到两张表,一张FactInternetSalesReason表,包含订单号salesordernumber和SalesReasonkey在内三个字段,共计64515条记录,另一张表DimSalesReason记录每个销售因素,包含SalesReasonkey和SalesReasonName在内四个字段,现在出现一个意外,这张表被不小心删除,只知道SalesReasonkey是自增列,最大值是10.

分析:首先明确要取的数据字段是什么,需求中说了是FactInternetSalesReason表中的SalesReasonkey这一个字段,不过需要筛选,筛选出不在DimSalesReason表中的记录,这看起来就是简单的条件筛选,用where语句not in关键字筛选就可以了,问题是DimSalesReason表的数据是缺失的,没有办法表连接查询。因此问题的关键在于造出DimSalesReason表的数据,虽然数据缺失,但是需求也告诉它是自增的,最大值是10,我们就可以使用递归方法生成数据表,再连接。

本篇是原创SQL解题总结系列第五篇,如果大家看了都知道SQL题大都有四个角度去考虑解法,但是对于这个问题就失灵了,因为它有特定的解法。上面分析了问题的关键在于怎么生成数据,这就是递归临时表的作用。

解法一 条件过滤

下面直接提供代码:

with recursive t as 
(select 1 as n union select n+1 as nn from t where n<=9)
select n 
from t 
where n not in (select SalesReasonkey 
               from (select SalesReasonkey,count(SalesReasonkey) 
                     from FactInternetSalesReason 
                     group by SalesReasonkey)a);

递归生成表和临时表作用是一样的,都需要用with as结构,不同的是recursive是生成表专门的保留字,这个递归表怎么理解它的写法呢?为什么union前只是取了一个数没有写from,而union后面有from和where条件呢?它是这样理解的:(select 1 as n)部分是这个整体(因为SQL可以直接用select取数,不需要后面写from),表示先生成第一条数据,并且生成表头(表结构和字段名),它是一张只有一行数据的表(将它初始化表名为t,字段名为n),union后面接的是另一张表,但这张表的每一行都是由上一行数据n再+1得到的,举个例子,union后面(select n+1 as nn from t where n<=9)是个整体,表示从t表取出n<=9的数据(需要where条件限定停止,不然就死循环了,而且为什么是n<=9,而不是需求中说的10呢?因为最终要查到的数据nn的取值n+1=10,所以n<=9),union前的表取出第一条数据,union后的表取出最后一条之前的所有数据,而中间的这些数据都是在第一条(select 1 as n)数据基础上一遍一遍迭代+1得到的,recursive的作用就是让临时表t不停的迭代。

还需要解释的是这里别名n和别名nn其实是一个意思,只不过n会作为引用进入迭代,如果后面一个别名还是n就会混淆,这也是为什么一般SQL语句表别名不能重复。这样解释是不是就很清晰了。

最后主SQL语句里对FactInternetSalesReason表里的SalesReasonkey进行了聚合计数,这是因为一个购买因素对应多个订单,我们通过对每个购买因素分组就可以找出唯一值(这种找唯一值也就是去重的方法效率比使用distinct保留字高,所以如果涉及到去重最好优先使用group使用分组),然后把这些唯一当做子查询作为条件来筛选不在里面的SalesReasonkey。

解法二 表连接

with recursive t as 
(select 1 as n union select n+1 as nn from t where n<=< span="">9)
select n 
from t 
left join (select SalesReasonkey,count(SalesReasonkey) 
          from FactInternetSalesReason 
          group by SalesReasonkey)a
on t.n = a.SalesReasonkey
where SalesReasonkey is null;

上面这段代码最核心的生成表的方式和前面是一样的,不同的是生成表之后的处理办法,这一种是将FactInternetSalesReason表中出现的SalesReasonkey分组计数,存在的购买因素计数肯定大于1,不存在的购买因素就为0。将t表生成数据与a表左连接,以t表字段n和a表字段SalesReasonkey为链接条件,根据需求可知a表的SalesReasonkey显然有不存在t表的,就可以用null来筛选。还有一种处理方式如下:

解法三 联合查询

这种连接的方法是将两张表相同字段纵向连接,然后分组计数,如果SalesReasonkey没有在FactInternetSalesReason表中它就只会在两张纵向联合的表里出现一次,如果分别在两张表都出现了,总次数就是两次。

with recursive 
t as (select 1 as n union 
 select n+1 as nn from t where n<=< span="">9),
t2 as (select SalesReasonkey,count(SalesReasonkey) 
      from FactInternetSalesReason 
      group by SalesReasonkey)
select n 
from (select n from t union all select SalesReasonkey from t2)a  
group by n having count(n) =1;

这就是解决这类问题的解法,关键就在递归部分的理解。

在使用with recursive过程中,还需要注意有以下几个限制:

1.如果在recursive term中使用left joiright join,自引用必须在“右”边。

2.recursive表中不允许使用group by和having/order by。

3.不允许在recursive语句的where语句的子查询中使用CTE的名字。

4.不支持在recursive语句中对CTE作聚合。

5.limit不允许在recursive语句中使用。

6.同时使用多个CTE表达式时,不允许多表达式之间互相访问,只能支持单向访问。

在使用CTE时也应注意如下几点:

1.CTE后面必须直接跟使用CTE的sql语句(如select、insert、update等),否则,CTE将失效。

2.CTE后面也可以跟其他的CTE,但只能使用一个with,多个CTE中间用逗号(,)分隔。

3.如果CTE的表达式名称与某个数据表或视图重名,则紧跟在该CTE后面的sql语句使用的仍然是CTE,当然,后面的sql语句使用的就是数据表或视图了。

4.CTE可以引用自身(递归查询),也可以引用在同一with子句中预先定义的 CTE。

最后欢迎大家关注我,我是拾陆,搜索公众号“二八Data”,更多技术干货持续奉献。

猜你喜欢

转载自blog.csdn.net/lqw844597536/article/details/121492287