Oracle SQL CTE(公用表表达式),其中没有数据

时间:2016-03-29 14:37:35

标签: sql oracle common-table-expression

我正在使用多个CTE在Oracle中构建一个非常复杂的SQL查询,后者的表达式依赖于前者的值。但是我发现如果其中一个先前的CTE不包含数据,则整个执行停止。例如:

WITH CTE1 AS
    (
    SELECT
        PEOPLE.ID AS PID,
        APPLICATIONS.DATE AS APPDATE
    FROM
        PEOPLE,
        APPLICATIONS
    WHERE
        APPLICATIONS.PERSON_ID = PEOPLE.ID
        AND APPLICATIONS.DATE BETWEEN TO_DATE('2015-01-01','YYYY-MM-DD') AND TO_DATE('2015-01-31','YYYY-MM-DD')
    ),
CTE2 AS
    (
    SELECT
        APPLICATIONS.PERSON_ID AS PID
        MIN(APPLICATIONS.DATE) AS EARLIEST_APPDATE
    FROM
        CTE1,
        APPLICATIONS
    WHERE
        APPLICATIONS.PERSON_ID = CTE1.PID
        AND APPLICATIONS.DATE < ADD_MONTHS(CTE1.APPDATE, -18)
    GROUP BY APPLICATIONS.PERSON_ID
    ),
MAIN_QUERY AS
    (
    SELECT
        CTE1.PID AS PID
    FROM
        CTE1, CTE2
    WHERE
        -- Note that the PIDs should either match, or should not exist in CTE2
        CTE1.PID = CTE2.PID OR (NOT EXISTS (SELECT PID FROM CTE2 WHERE CTE1.PID = CTE2.PID))
    )
SELECT
    MAIN_QUERY.PID
FROM MAIN_QUERY

当然,我意识到上面的例子本身就完全没有意义,但是我刚刚简化了这个例子来说明问题。 CTE2返回申请日期超过CTE1申请日期前18个月的同一人员身份证申请的最早日期。但是......如果没有这样的申请怎么办? CTE2能够返回零行。

您会注意到CTE2本身并未在最终查询中引用。在MAIN_QUERY中处理空CTE2。因此,关于最终查询,CTE2是否实际返回任何行都无关紧要。

然而,当CTE2没有行时,我正在使用的应用程序(Business Objects)会抛出一个错误,即查询“没有要获取的数据”。

我想找到一种解决方法,即使CTE2返回null也能使我的查询执行。感谢。

1 个答案:

答案 0 :(得分:1)

  

CTE2返回申请日期超过CTE1申请日期前18个月的同一人员身份证申请的最早日期。但是......如果没有这样的申请怎么办? CTE2能够返回零行。

您可以使用简单的分析功能替换sub-query factoringWITH ... AS ( ... ))子句:

Oracle安装程序

CREATE TABLE PEOPLE ( id ) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 3;

CREATE TABLE APPLICATIONS ( id, person_id, "DATE" ) AS
SELECT 1, 1, DATE '2015-01-01' FROM DUAL UNION ALL -- First row to return
SELECT 2, 1, DATE '2014-01-01' FROM DUAL UNION ALL -- Within 18 months
SELECT 3, 1, DATE '2013-01-01' FROM DUAL UNION ALL -- Before 18 months
SELECT 4, 1, DATE '2012-01-01' FROM DUAL UNION ALL -- Before 18 months and min
SELECT 5, 2, DATE '2015-01-02' FROM DUAL UNION ALL -- Second row to return
SELECT 6, 2, DATE '2014-01-02' FROM DUAL UNION ALL -- Within 18 months
SELECT 7, 3, DATE '2015-01-03' FROM DUAL UNION ALL -- Third row to return
SELECT 8, 3, DATE '2013-07-03' FROM DUAL;          -- Exactly 18 months earlier

<强>查询

SELECT PID,
       APPDATE,
       CASE EARLIEST_APPDATE
            WHEN APPDATE - INTERVAL '18' MONTH
            THEN NULL
            ELSE EARLIEST_APPDATE
            END AS EARLIEST_APPDATE -- Included for the edge case where
                                    -- EARLIEST_APPDATE is exactly 18 months
                                    -- earlier as the RANGE BETWEEN is
                                    -- inclusive.
FROM   (
  SELECT p.ID AS PID,
         a."DATE" AS APPDATE,
         MIN( a."DATE" ) OVER ( PARTITION BY p.ID
                                ORDER BY     a."DATE"
                                RANGE BETWEEN UNBOUNDED PRECEDING
                                          AND INTERVAL '18' MONTH PRECEDING )
           AS EARLIEST_APPDATE
  FROM   PEOPLE p
         INNER JOIN APPLICATIONS a
         ON ( a.PERSON_ID = p.ID )
)
WHERE  APPDATE BETWEEN DATE '2015-01-01' AND DATE '2015-01-31'

<强>输出

       PID APPDATE             EARLIEST_APPDATE  
---------- ------------------- -------------------
         1 2015-01-01 00:00:00 2012-01-01 00:00:00 
         2 2015-01-02 00:00:00                     
         3 2015-01-03 00:00:00