SQL-递归查询在Ora与Mssql

Posted dimink

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SQL-递归查询在Ora与Mssql相关的知识,希望对你有一定的参考价值。

       今天在工作中,有同事“请教”从 Sql Server 移植数据到 DM DB 的改写问题,本以为难度不大,结果发现 Sql Server 数据库的语法、架构上,与 Oracle / DM 数据库差异还是蛮大的,弄得没怎么用过 Sql Server 的我十分头大,赶紧记载一些工作中遇到的问题,也加固 SQL 的学习,像递归查询这个问题,对我来说一直都是一个比较生涩的点。   

       在 Mssql 中,递归的语法是用 With 来实现的,所以一开始我还以为是要用 With 生成临时结果来处理,后来在网上查找了一下资料,才知道语法就是这样的。可以参考 TurboWay 博客(https://www.cnblogs.com/TurboWay/p/7728746.html)中的说法,于是我在笔记本上装了一个 Sql Server 2017 进行测试,为了方便,就采用了博客中的脚本进行测试。  

/*
test表
ID        地区ID
Name      地区名称 
Main_ID   地区所属上级ID
Sign      地区等级 例如:福建-厦门-湖里 分别是 1,2,3
*/
-- 建表
SELECT     1003    ID,    福建    Name,    0    Main_ID,    1 Sign INTO test    union all
SELECT     1050    ,    福州    ,    1003    ,    2    union all
SELECT     1051    ,    厦门    ,    1003    ,    2    union ALL
SELECT     1375    ,    思明    ,    1051    ,    3    union all
SELECT     1382    ,    海沧    ,    1051    ,    3    union all
SELECT     1381    ,    湖里    ,    1051    ,    3    union all
SELECT     1374    ,    集美    ,    1051    ,    3    union all
SELECT     1373    ,    同安    ,    1051    ,    3    union all
SELECT     1380    ,    翔安    ,    1051    ,    3    union ALL
SELECT     667582720122    ,    鼓楼    ,    1050    ,    3    union all
SELECT     667582725528    ,    台江    ,    1050    ,    3    union all
SELECT     667582729587    ,    仓山    ,    1050    ,    3    union all
SELECT     667582732602    ,    马尾    ,    1050    ,    3    union all
SELECT     667582735385    ,    晋安    ,    1050    ,    3    union all
SELECT     667582738507    ,    闽侯    ,    1050    ,    3    union all
SELECT     667582742586    ,    连江    ,    1050    ,    3    union all
SELECT     667582745634    ,    罗源    ,    1050    ,    3    union all
SELECT     667582748358    ,    闽清    ,    1050    ,    3    union all
SELECT     667582751824    ,    永泰    ,    1050    ,    3    union all
SELECT     667582755215    ,    平潭    ,    1050    ,    3    union all
SELECT     667582760309    ,    福清    ,    1050    ,    3    union all
SELECT     667582764565    ,    长乐    ,    1050    ,    3;

      这里才发现,在 Mssql 中,可以通过这样创建表,有点类似 Oracle 语法中的 Create Table as Select 的方式。通过导出 DDL 可以看到表 test 中列的精度。  

CREATE TABLE [dbo].[test](
    [ID] [numeric](12, 0) NOT NULL,
    [Name] [varchar](4) NOT NULL,
    [Main_ID] [int] NOT NULL,
    [Sign] [int] NOT NULL
) ON [PRIMARY]
GO

发现 number 类型和 varchar 类型,都是刚好是 id 列和 name 列的最大精度。表环境拟好以后,可以执行 SQL。  

WITH CTE AS 
(
--父项
SELECT  a.*,1 level 
FROM test a WHERE ID=1003
UNION ALL 
--递归结果集中的下级 
SELECT a.* , level + 1 as level
FROM test a
INNER JOIN CTE b ON b.ID=a.Main_ID
)select * from CTE;

--结果集
ID                                      Name Main_ID     Sign        level
--------------------------------------- ---- ----------- ----------- -----------
1003                                    福建   0           1           1
1050                                    福州   1003        2           2
1051                                    厦门   1003        2           2
1375                                    思明   1051        3           3
1382                                    海沧   1051        3           3
1381                                    湖里   1051        3           3
1374                                    集美   1051        3           3
1373                                    同安   1051        3           3
1380                                    翔安   1051        3           3
667582720122                            鼓楼   1050        3           3
667582725528                            台江   1050        3           3
667582729587                            仓山   1050        3           3
667582732602                            马尾   1050        3           3
667582735385                            晋安   1050        3           3
667582738507                            闽侯   1050        3           3
667582742586                            连江   1050        3           3
667582745634                            罗源   1050        3           3
667582748358                            闽清   1050        3           3
667582751824                            永泰   1050        3           3
667582755215                            平潭   1050        3           3
667582760309                            福清   1050        3           3
667582764565                            长乐   1050        3           3

  其实,从这个结果集就很好理解递归查询应用场景,以地点这个场景来解释层次查询是非常适合的,如果表中没有 Sign 这个列,在 Sql 中的 level 也能非常好的分层。第一层就是福建,福建下面的第二层地点就是福州和厦门, Sign = 3 的就是第三层地名。把它们关联起来的是Id 与 Main_id ,一般我们把 Main_id 叫 Parent id ,即它的父节点 id 。比如地点马尾,它的 Main_id 是1050,而 id 是1050的就是马尾的父节点——福州。如果我们把整个结果集当成一棵树的结构的话,在以上 Sql 中,是从 id = 1003 开始,遍历整棵树,Sign = 1 是根的话,Sign = 3 的就是这棵树的叶,这样的查询就是自顶向下(树形结构是倒着的)的一种层次查询。(仅个人理解,可能不准确)
  而下面这样,就是自底向上的查询了,通过一个叶子节点去查找父节点。也很好理解,第一部分先获取到叶子节点的 id,第二部分,通过已经查到的叶子节点数据(cte as b)的父节点,来找父节点的数据(b.Main_id = test.id)。  

with cte as (
select a.*,1 as level from test a where id = 667582751824
union all
select a.*,level + 1 as level from test a inner join cte as b
 on a.id = b.Main_ID
) select * from cte;

--结果集
ID                                      Name Main_ID     Sign        level
--------------------------------------- ---- ----------- ----------- -----------
667582751824                            永泰   1050        3           1
1050                                    福州   1003        2           2
1003                                    福建   0           1           3

       在 Oracle 语法中,层次查询的语法与 Mssql 中不一样,以前在用到的时候,一直难以理解是怎么个关联关系的,直到在一个博客中看到一种说法:prior 就是"找"的意思,prior id 就是找叶子节点,自顶而下查询;prior parent_id 就是找父节点,即自底向上查询。这样解释起来就非常有意思而且便于记忆,是个非常棒的点。以下是 Oracle / DM 对应的SQL。  

select * from test start with id=1003 connect by prior id= main_id;
select a.*,level from test a start with id = 667582735385 connect by prior main_id = id;

 

以上是关于SQL-递归查询在Ora与Mssql的主要内容,如果未能解决你的问题,请参考以下文章

Microsoft SQL Server 代码片段收集

oracle 表删除出错 ORA-00604: 递归 SQL 级别 1 出现错误 ORA-01426: 数字溢出

递归sql级别1出现错误

ms sql 2005 递归查询如何实现

关于SQL递归查询问题

sql MSSQL片段