`

从 Oracle 移植递归 SQL 到 DB2 UDB

阅读更多
reference:http://www.cnitblog.com/iniboy/archive/2006/06/05/11622.html

   简介

递归 SQL 是用于查询数据层次结构的一种非常强大的方式。组织结构(部门、子部门、子子部门,等等)、讨论论坛(发贴、响应、对响应的响应,等等)、原料帐单、产品分类以及文档层次结构都是层次型数据的例子。

IBM® DB2® Universal Database™ (UDB)是实现了递归 SQL 的几种关系数据库产品中的一种。通常,可以将 DB2 方法看作一种高度强大和灵活的实现。DB2 在递归优势上的一个体现就是在单个的 DB2 表中查询多个层次结构的能力。(要了解更多这方面的细节,请参考在 DB2 开发者园地(DB2 Developer Domain)上由 Srini Venigalla 撰写的文章 使用 DB2 v7.2 中的 SQL UDF 扩大递归机会 。

如果您要将数据从一个 RDBMS 移植到另一个 RDBMS,那么重要的是要知道递归 SQL 的实现因产品而异。特别地,在 Oracle 与 DB2 UDB 之间的差异 这一部分,我将解释在将项目从 Oracle 移植到 DB2 并且涉及递归 SQL 时经常会出现的一个问题。

最根本的问题就是,在 Oracle 和 DB2 中,查询的默认排序次序各不相同。乍一看来这并不重要,因为通常应用程序并不十分依赖于默认的排序次序(没有使用 ORDER BY 子句)。然而在实际中,需要用 Oracle 提供的默认排序次序来解决许多问题,例如显示讨论的线索。很多应用程序都是基于 Oracle 的排序次序的假设,因而当要将那些应用程序移植到 DB2 UDB 时,要理解这一点。

当然,除了解释这个问题之外,我还会给出针对 DB2 中这一难题的解决方案的要点。要看这方面的内容,参见 在 DB2 UDB 中仿效 Oracle 的行为这一部分。

为了给读者提供有关一般递归,尤其是递归 SQL 的一些背景信息,我将从简要地介绍 DB2 递归 SQL 开始我们的话题。

递归 SQL 如何工作?

递归通常表现为三个基本的步骤:

   1. 初始化。
   2. 递归,或者在整个层次结构中重复对逻辑的迭代。
   3. 终止。

在初始步骤中,要准备好工作区域,并用初始值设置好变量。递归由工作区域中的商业逻辑操作以及随后对下一递归的调用组成,这里采用一种嵌套的方式。最后,终止步骤用于限定递归。打个比方,可以理解为对嵌套级数进行计数,当达到某一特定级数时便停止执行。

这一原理也可以应用到 DB2 中的递归 SQL。递归 SQL 是一种可以分为三个执行阶段的查询:

   1. 创建初始结果集。
   2. 基于现有的结果集进行递归。
   3. 查询完毕,返回最终的结果集。

初始的结果集建立在对基本表的常规 SQL 查询的基础上,这是公共表表达式(CTE)的第一部分。公共表表达式是用于支持递归的手段,它的第二部分对自己进行调用并将其与基本表相连接。从该 CTE 中进行选择的查询便是终止步骤。

下面的例子演示了这一过程。DEPARTMENT是一个包含了有关某个部门的信息的表:


CREATE TABLE departments (deptid INT,
  deptname VARCHAR(20),
  empcount INT,
  superdept INT)





这个表的内容代表了一个层次结构。下面的 图 1就是一个例子:

图 1. 一个表层次结构的例子
Table hierarchy

对于一个给定的部门,该部门包括所有的子部门,要获得该部门的雇员人数,需要一个递归查询:


WITH temptab(deptid, empcount, superdept) AS
   (    SELECT root.deptid, root.empcount, root.superdept
            FROM departments root
            WHERE deptname='Production'
     UNION ALL
        SELECT sub.deptid, sub.empcount, sub.superdept
            FROM departments sub, temptab super
            WHERE sub.superdept = super.deptid
   )
SELECT sum(empcount) FROM temptab





在这个例子中,CTE 被称作 temptab,随着查询的继续执行,temptab 会逐渐变大。下面给出了所有的递归元素:

   1. 在 temptab 中建立初始结果集。它包含了部门“Production”的雇员人数:

      SELECT root.deptid, root.empcount, root.superdept
                  FROM departments root
                  WHERE deptname='Production'


   2. 当在 temptab 中针对于各个子部门加入每一行记录时,便发生了递归。该递归每一次执行的结果都通过 UNION ALL 加入到 temptab 中:

      SELECT sub.deptid, sub.empcount, sub.superdept
                  FROM departments sub, temptab super
                  WHERE sub.superdept = super.deptid


   3. 最后的查询就是从 CTE 中提取出所需的信息。在本例中,进行的是总计操作:

      SELECT sum(empcount) FROM temptab


下面是例子查询的结果:


1
-----------
SQL0347W  The recursive common table expression "TORSTEN.TEMPTAB" may contain
an infinite loop.  SQLSTATE=01605
         50
  1 record(s) selected with 1 warning messages printed.





通过 DB2 解释工具可以检查 DB2 是如何执行这种递归查询的。嵌套的循环连接(NLJOIN)以一个临时结果表(TEMP)为基础,而这次连接的的结果又再次通过 UNION 被放到这个临时表中。

图 2. 对递归 SQL 的解释
Explain of recursive SQL




回页首


Oracle 与 DB2 UDB 之间的差异

Oracle 通过使用 CONNECT BY PRIOR 提供了类似的特性。在 Oracle 中,上面的例子可以这样来实现:


SELECT sum(empcount) FROM STRUCREL
   CONNECT BY PRIOR superdept = deptid
     START WITH deptname = 'Production';





除了语法上的不同之外,DB2 与 Oracle 在功能性上也有差异。当使用 CONNECT BY PRIOR 时,Oracle 提供了内建的伪列 level。在 Oracle 中,下面的查询提供了所有的部门以及这些部门所在的层次结构:


SELECT deptname, level FROM departments
  CONNECT BY PRIOR superdept = deptid
  START WITH deptname = 'Samples & Co.';
DEPTNAME             LEVEL
-------------------- -----------
Samples & Co.                  1
Production                     2
QA                             3
Manufacturing                  3
Prebuilding                    4
Finalbuilding                  4
Sales                          2
North                          3
East                           3
South                          3
West                           3
IT                             2





这种伪列通常用于限制那些查询的递归深度。例如,为了检索“Sales”这个部门的直属子部门,在 Oracle 中可以使用下面的查询:


SELECT deptname FROM departments CONNECT BY PRIOR superdept = deptid
START WITH deptname = 'Sales' AND level=2;
DEPTNAME
--------------------
North
East
South
West





在 DB2 中可以轻易地仿效这一特性,只需像下面这样在 CTE 中维护一个自定义的伪列:


WITH temptab(deptid, deptname, superdept, level) AS
   (    SELECT root.deptid, root.deptname, root.superdept, 1
            FROM departments root
            WHERE deptname='Sales'
     UNION ALL
        SELECT sub.deptid, sub.deptname, sub.superdept, super.level+1
            FROM departments sub, temptab super
            WHERE sub.superdept = super.deptid
   )
SELECT deptname FROM temptab WHERE level=2;





除了 level 伪列,在 DB2 和 Oracle 中另一个非常重要的差异就是由递归查询生成的结果集的搜索次序。在 Oracle 中,层次结构是由深度优先算法创建的。这样一来,当检索整个例子层次结构时,产生的结果集就是这个样子:


SELECT deptname, level FROM departments CONNECT BY PRIOR superdept = deptid
START WITH deptname = 'Samples & Co.;
DEPTNAME             LEVEL
-------------------- -----------
Samples & Co.                  1
Production                     2
QA                             3
Manufacturing                  3
Prebuilding                    4
Finalbuilding                  4
Sales                          2
North                          3
East                           3
South                          3
West                           3
IT                             2





这个结果集说明,在查询延伸到邻节点之前,先要浏览完每个子节点。然而,在 DB2 中,层次结构是通过广度优先算法创建的:


WITH temptab(deptid, deptname, superdept, level) AS
   (    SELECT root.deptid, root.deptname, root.superdept, 1
            FROM departments root WHERE deptname='Samples & Co.'
     UNION ALL
        SELECT sub.deptid, sub.deptname, sub.superdept, super.level+1
            FROM departments sub, temptab super
            WHERE sub.superdept = super.deptid
   )
SELECT deptname, level FROM temptab;
DEPTNAME             LEVEL
-------------------- -----------
Samples & Co.                  1
Production                     2
Sales                          2
IT                             2
QA                             3
North                          3
East                           3
South                          3
West                           3
Manufacturing                  3
Prebuilding                    4
Finalbuilding                  4





这意味着,结果集是一级一级地创建的。在本例中,这种差异或许算不了什么。但是在有些递归 SQL 的案例中,默认的排序次序则是至关重要的。例如,有一个包含讨论论坛的表:


CREATE TABLE discussion (postid INTEGER,
superid INTEGER,
title VARCHAR2(100),
text VARCHAR2(1000) )





为了获得对所有讨论线索的了解,在 Oracle 中可以这样来查询这个表:


SELECT RPAD('', level-1, '--') || title FROM discussion
CONNECT BY PRIOR superid = postid START WITH postid = 0;
1
-------------------------------------
Install Problem
--Re: Install Problem
----Re: Install Problem
------Re: Install Problem
--------Got it
--General comment
----Re: General Comment
Cannot find file
--Re: Cannot find file
----Re: Cannot find file
--Re: Cannot find file
Help! Documentation missing!





在 DB2 中使用无格式(plain)递归 SQL 时,不能以这样的次序重新得到结果集。如果非要尝试这么做的话,将得到下面的结果:


WITH temptab(superid, postid, title, text, level)
     AS
     (    SELECT root.superid, root.postid, root.title, root.text, 1
              FROM discussion root
              WHERE postid=0
       UNION ALL
          SELECT sub.superid, sub.postid, sub.title, sub.text, super.level+1
             FROM discussion sub, temptab super
             WHERE sub.superid = super.postid
     )
SELECT VARCHAR(REPEAT('--', level-1) || title , 60) FROM temptab;
1
------------------------------------
Problem Discussions
--Install Problem
--Cannot find file
--Help! Documentation missing!
----Re: Install Problem
----General comment
----Re: Cannot find file
----Re: Cannot find file
------Re: Install Problem
------Re: General Comment
------Re: Cannot find file
--------Re: Install Problem
----------Got it





显然,对于用户来说该结果集完全没有用,因为这里失去了论坛上各个帖子之间的相关性。




回页首


DB2 UDB 中仿效 Oracle 的行为

在 DB2 中,要生成 Oracle 中那样的深度优先次序,解决方案的基础就是引入一个附加的伪列,这个伪列可以在 ORDER BY 属性中使用。这个列的类型是 VARCHAR,包含了到每个节点的路径,其格式为“1.3.1”。另外还引入了一个用户定义的表函数,这个函数可以返回一个给定节点的所有子节点。通过将子节点的序号连接到上级节点的路径上,能够可靠地维护伪列代码。可以使用 DB2 的 RANK() 函数来检索一个子节点的序号。之后,递归查询从这个函数中进行选择,并提供当前节点的 id 以及它的路径作为输入。

下面的例子将创建与上一例子中 Oracle 中的查询完全一致的结果集:


CREATE FUNCTION GetResponses(code VARCHAR(100), superid INTEGER)
   RETURNS TABLE(code VARCHAR(100), superid INTEGER, postid INTEGER,
                 title VARCHAR(100), text VARCHAR(1000))
   READS SQL DATA DETERMINISTIC NO EXTERNAL ACTION
   RETURN SELECT GetResponses.code || '.'
                  || RTRIM(CHAR(RANK() OVER (ORDER BY postid))),
                 T.superid , T.postid, T.title, T.text
              FROM discussion T
              WHERE T.superid = GetResponses.superid;
WITH TEMPTAB(code, superid, postid, title, text, level)
     AS
     (    VALUES(CAST('1' AS VARCHAR(100)), CAST(NULL AS INTEGER), 0, 
                 CAST(NULL AS VARCHAR(100)), CAST(NULL AS VARCHAR(1000)), 0)
       UNION ALL
          SELECT t.code, t.superid, t.postid, t.title, t.text, level+1
             FROM TEMPTAB,
               TABLE(GetResponses(TEMPTAB.code, TEMPTAB.postid)) AS T
     )
SELECT VARCHAR(REPEAT('--', level-1) || title , 60)
     FROM TEMPTAB T
     WHERE t.superid is not null
     ORDER BY code;
1
-------------------------------------
Install Problem
--Re: Install Problem
----Re: Install Problem
------Re: Install Problem
--------Got it
--General comment
----Re: General Comment
Cannot find file
--Re: Cannot find file
----Re: Cannot find file
--Re: Cannot find file
Help! Documentation missing!





为了使应用程序中的语句简单一些,这里同样可以将这些递归语句包装到一个 UDF 中。




回页首


一种更好地使用 DB2 Node 类型的方法

您必须清楚,基于一个字符串使用伪列以强制性地使结果集具有某一特定的次序,这只能保证总体上的层次结构次序。如果某个节点的直属子节点的数量超过 9 的话,这样做未必能够正确地对这些子节点排序。这是因为像“1.2.13”这样的字符串比“1.2.13”有着更低的次序。但是从语义上讲,事情刚好相反。如果您要依赖于这种方法,而又不能保证最多只有 9 个直属子节点,那么您就决不能为伪列使用一个字符串。

相反,您可以使用 DB2 Node 类型,这是一个 DB2 扩展,当前在 IBM DB2 Developer Domain 上(由 Jacques Roy 撰写的 Using the Node Data Type to Solve Problems with Hierarchies in DB2 Universal Database )可以获得。您必须使用最低版本为 1.1 的 Node 类型扩展。可以通过 nodeVersion() 函数来检查版本。如果该函数不存在,那么就说明您使用的是更老版本的 DB2 Node 类型。

因此,现在我们不使用 VARCHAR 类型来维护伪列代码,而是使用用户定义类型的 Node。下面的例子对此作了演示。该例子将创建与上面使用 VARCHAR 的例子一样的结果集:


CREATE FUNCTION GetResponsesN(code Node, superid INTEGER)
   RETURNS TABLE(code Node, superid INTEGER, postid INTEGER,
                 title VARCHAR(100), text VARCHAR(1000))
   READS SQL DATA DETERMINISTIC NO EXTERNAL ACTION
RETURN SELECT nodeInput(nodeOutput(GetResponsesN.code) || '.' || 
                                 RTRIM(CHAR(RANK() OVER (ORDER BY postid)))),
              T.superid , T.postid, T.title, T.text
         FROM discussion T
         WHERE T.superid = GetResponsesN.superid;
WITH TEMPTAB(code, superid, postid, title, text, level)
     AS
     (    VALUES(nodeInput('1.1'), CAST(NULL AS INTEGER), 0, 
                 CAST(NULL AS VARCHAR(100)), CAST(NULL AS VARCHAR(1000)), 0)
       UNION ALL
          SELECT t.code, t.superid, t.postid, t.title, t.text, level+1
             FROM TEMPTAB,
               TABLE(GetResponsesN(TEMPTAB.code, TEMPTAB.postid)) AS T
     )
SELECT VARCHAR(REPEAT('--', level-1) || title , 60)
     FROM TEMPTAB T
     WHERE t.superid is not null
     ORDER BY code;
1
-------------------------------------
Install Problem
--Re: Install Problem
----Re: Install Problem
------Re: Install Problem
--------Got it
--General comment
----Re: General Comment
Cannot find file
--Re: Cannot find file
----Re: Cannot find file
--Re: Cannot find file
Help! Documentation missing!





为了创建一个 Node 值,我们必须使用函数 nodeInput(),并为之提供像“1.2” 这样的一个字符串作为输入。对于根节点,输入是“1.1”(由于 DB2 节点类型的具体实现,我们只能从 1.1 开始,而不是从 1 开始)。对于所有其他的节点,我们同样使用 DB2 的 RANK() 函数来为直属子节点分配序号。这是在 GetResponsesN() 函数中进行的。之后,这个序号被连接到上级节点的字符表示(通过 nodeOutput() 获得)上,再将这样得到的字符串作为输入,通过 nodeInput() 函数创建新的 Node 值。




回页首


结束语

DB2 UDB 为递归 SQL 而设的方法提供了一种非常灵活的方式来处理层次结构。正如本文所演示的,DB2 UDB 能够轻易地仿效其他数据库供应商的行为,因为 DB2 通过用户定义的函数提供了方便自如的可扩展性。而且,DB2 UDB 还提供了处理非常高级的递归查询的方法,例如那些在单个表中有多重层次结构的情况下的递归查询。通过使用我描述过的这些技术,在移植应用程序时您可以充分利用 DB2 的长处。




回页首


免责声明

本文包含示例代码。IBM 授予您(“被许可方”)使用这个样本代码的非专有的、版权免费的许可证。然而,该样本代码是以“按现状”的基础提供的,没有任何形式的(不论是明示的,还是默示的)保证,包括对适销性、适用于特定用途或非侵权性的默示保证。IBM 及其许可方不对被许可方由于使用该软件所导致的任何损失负责。任何情况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许可方都不对由使用该软件或不能使用该软件所引起的收入的减少、利润的损失或数据的丢失,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使 IBM 已经被明确告知此类损害的可能性,也是如此。




回页首


致谢

感谢 Serge Rielau (IBM)为我指引了正确的方向;感谢 Jacques Roy (IBM)认真审查并正确无误地输入了这篇文档;同时也感谢 Jacques Terrasse (Aldata)为我提供了机会,使我得以在客户环境中得出这一解决方案。
分享到:
评论

相关推荐

    Oracle递归SQL学习

    Oracle数据库中的递归SQL是一种强大的工具,用于处理层次结构数据,如组织结构、分类树或者层级关系等。在本主题中,我们将深入探讨如何利用递归SQL在Oracle中展示一棵树形结构,以及如何根据父ID遍历所有的子ID。 ...

    oracle-递归查询地区名称

    oracle 递归调用 地区 ,用到的方法是 SYS_CONNECT_BY_PATH,可以扩展层级 名称

    sql_函数实现三种父子递归

    在SQL中,递归函数是处理层次结构数据的有效方法,特别是在构建树形结构如菜单树或权限树时。本文将探讨如何使用SQL函数实现三种常见的递归查询:找到所有子节点、查找所有父节点以及面包屑导航数据。我们将通过一个...

    Oracle 递归函数介绍

    Oracle 递归函数是一种特殊的PL/SQL函数,可以用于解决复杂的树形结构查询问题。递归函数可以自我调用,以便遍历树形结构的每个节点,直到达到停止条件。 在 Oracle 中,递归函数的定义语法如下所示: ```sql ...

    oracle递归、迭代

    ### Oracle中的递归查询详解 #### 一、引言 在数据库管理中,处理具有层次结构的数据是一项常见的任务。例如,在组织结构、产品分类或文件系统等场景中,经常需要查询这种类型的层级数据。Oracle数据库提供了强大...

    oracle SQL递归的使用详解

    在Oracle数据库中,SQL递归查询是一种非常有用的工具,它允许我们处理层级关系的数据,例如组织结构、树形目录或有上下级关联的数据。本文将详细介绍如何在Oracle中使用SQL递归语句,并通过示例进行说明。 首先,...

    将 CONNECT BY 移植到 DB2

    这一转换对于从 Oracle 迁移到 DB2 的应用尤为重要,因为两种数据库在处理递归查询方面有着显著的不同。 #### 递归查询概述 递归查询用于解决层级关系数据问题,例如查找组织结构中的员工和他们的上级主管。Oracle ...

    在db2和oracle中的对树的递归查询语句

    同时,为了防止无限递归,通常会设置一个最大递归深度,比如Oracle的`CONNECT_BY_ISCYCLE`和DB2的`WITH RECURSIVE`中的循环检测。 在实际应用中,了解并熟练掌握这些递归查询技巧对于处理树形结构的数据至关重要。...

    DB2递归实现

    DB2递归实现使用逐层递归(广度优先)方法,而Oracle递归实现使用深度优先方法。 在本文中,我们将使用一个实用的例子来描述如何将CONNECT BY和相关的伪列映射到DB2递归。我们将定义和填充一个雇员表,然后使用递归...

    DB2 UDB AS400版数据性能和查询优化

    DB2 UDB AS/400版本的数据性能和查询优化涉及多个方面,从SQL语句的编写到系统配置的调整,再到数据结构的设计都需要综合考虑。通过实施上述策略和技术,可以有效提升数据库的整体性能,满足日益增长的数据处理需求...

    Oracle递归树形结构查询功能

    Oracle数据库在处理树形结构数据时提供了强大的递归查询功能,这种特性对于组织结构、产品分类、层级菜单等场景的应用非常广泛。递归树形结构查询主要依赖于`CONNECT BY`和`PRIOR`关键字,它们允许我们构建复杂的...

    Oracle递归查询

    通过使用`START WITH`和`CONNECT BY PRIOR`关键字,我们可以轻松地实现从上级到下级、从下级到上级或两表连接等多种类型的递归查询。希望本文能够帮助您更好地理解和应用Oracle中的递归查询技术。

    递归sql.sql

    mysql 数据库的sql语句递归查询,记录文档,已经设置变量等

    地区表MySql递归sql脚本

    地区表MySql递归sql脚本

    递归查询菜单树,支持mysql,oracle

    这个查询首先定义了一个递归公共表表达式(CTE),从顶层菜单开始,然后在每次递归调用中,将子菜单添加到结果集中。 Oracle数据库也支持递归查询,但使用的是`CONNECT BY`语句。对于同样的`menus`表,Oracle的查询...

    oracle递归查询向上向下.txt

    oracle递归查询

    ORACLE和SQL Server的语法区别

    ### ORACLE和SQL Server的语法区别 #### 一、概述 本文主要介绍Oracle与SQL Server在SQL语言层面的异同之处,...通过遵循上述指导原则,可以有效地将Oracle应用程序移植到SQL Server环境中,同时保持其功能性和性能。

    ORACLE转DB2对照全解

    **注意:** Oracle提供了`CONNECT BY`语法来实现层次查询,而DB2则通过递归公共表表达式(CTE)来实现。 #### 九、打印输出信息 **Oracle:** ```sql DBMS_OUTPUT.PUT_LINE('Hello World'); ``` **DB2:** ```sql ...

    ORACLE与SQLSERVER语法差异分析

    理解这些语法差异对于数据库管理员和开发人员来说至关重要,因为它们直接影响到SQL查询的编写和数据库应用程序的移植性。在跨平台开发或迁移项目时,必须充分了解并适应这些差异,以确保代码的正确性和效率。

    oracle递归查询的例子

    在数据库查询语言中,Oracle 提供了一种强大的功能——递归查询,这在其他数据库系统如 SQL Server 中是缺失的功能。递归查询允许用户执行多级关联查询,特别适用于处理具有层次结构的数据,例如组织结构图、文件...

Global site tag (gtag.js) - Google Analytics