Oracle SQL:简化我看似可怕的子查询集合

Oracle SQL: Simplifying my seemingly-horrendous subquery collection

我有一个相当大的查询用于获取大量结果,我几乎可以肯定这不是实现它的方法。很脏。这是可恶的。首先,让我解释一下我想要的 table 结构:

+------------------+------+------+------+------+--------+
|   CURRENT_DATE   |  RO  |  FL  |  LM  |  AO  |  TOTAL |
+------------------+------+------+------+------+--------+
|    1/2/2012      |  31  |  33  |  70  |  10  |   144  |
+------------------+------+------+------+------+--------+

以上数据集采集自以下table:

+---------------+--------------------+
|  CURRENT_DATE |  PORTABLE_PEANUTS  |
+---------------+--------------------+
|   1/2/2012    |         RO         |
+---------------+--------------------+
|   2/4/2013    |         FL         |
+---------------+--------------------+
|   3/6/2014    |         LM         |
+---------------+--------------------+
|   4/8/2015    |         AO         |
+---------------+--------------------+

本质上,我试图收集在某个特定日期发生在 PORTABLE_PEANUTS 上的所有事情、发生的频率以及内部发生的事情。

这是我正在使用的查询:

SELECT total.CURRENT_DATE, results.RO, results.FL, results.LM, results.AO, total.TOTAL FROM 
(
    SELECT CURRENT_DATE, SUM(RO+FL+LM+AO) TOTAL FROM 
    (
        SELECT a.CURRENT_DATE, a.RO, b.FL, c.LM, d.AO FROM 
        (
            SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) RO FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'RO'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')
    ) a
    JOIN
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) FL FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'FL'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')    
    ) b ON a.CURRENT_DATE = b.CURRENT_DATE
    JOIN
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) LM FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'LM'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')    
    ) c ON a.CURRENT_DATE = c.CURRENT_DATE
    JOIN
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) AO FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'AO'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')    
    ) d ON a.CURRENT_DATE = d.CURRENT_DATE
) 
GROUP BY CURRENT_DATE
) total
JOIN
(
    SELECT a.CURRENT_DATE, a.RO, b.FL, c.LM, d.AO FROM 
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) RO FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'RO'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')
    ) a
    JOIN
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) FL FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'FL'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')    
    ) b ON a.CURRENT_DATE = b.CURRENT_DATE
    JOIN
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) LM FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'LM'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')    
    ) c ON a.CURRENT_DATE = c.CURRENT_DATE
    JOIN
    (
        SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') CURRENT_DATE, COUNT(PORTABLE_PEANUTS) AO FROM CORE.DATE_TEST
            WHERE PORTABLE_PEANUTS LIKE 'AO'
        GROUP BY  TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')    
    ) d ON a.CURRENT_DATE = d.CURRENT_DATE
) results ON total.CURRENT_DATE = results.CURRENT_DATE
ORDER BY CURRENT_DATE ASC; 

现在这个查询可以用了,相对来说也够快了,就是比较难看。它看起来很难维护,而且我很确定我在这里遗漏了一些东西。

我认为您可以简单地使用数据透视查询 - 假设您使用的是 Oracle 11g 或更高版本,例如:

with date_test as (select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'FL' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'FL' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'FL' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'LM' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'LM' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'LM' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual)
-- end of mimicking sample data
select to_char(session_date, 'yyyy-mm-dd') current_date,
       ro,
       fl,
       lm,
       ao,
       ro + fl + lm + ao total
from   date_test
pivot (count(portable_peanuts)
       for portable_peanuts in ('RO' as ro, 'FL' as fl, 'LM' as lm, 'AO' as ao))
order by to_char(session_date, 'yyyy-mm-dd');

CURRENT_DATE         RO         FL         LM         AO      TOTAL
------------ ---------- ---------- ---------- ---------- ----------
2012-02-01            2          2          0          3          7
2012-02-02            2          1          3          2          8

或者,如果您使用的是 10g 或更早版本,则可以使用 case 语句和聚合的旧式方法来进行数据透视:

with date_test as (select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'RO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'FL' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'FL' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'FL' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'LM' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'LM' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'LM' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('01/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual union all
                   select to_date('02/02/2012', 'dd/mm/yyyy') session_date, 'AO' portable_peanuts from dual)
-- end of mimicking sample data
select   to_char(session_date, 'yyyy-mm-dd') current_date,
         count(case when portable_peanuts = 'RO' then 1 end) ro,
         count(case when portable_peanuts = 'FL' then 1 end) fl,
         count(case when portable_peanuts = 'LM' then 1 end) lm,
         count(case when portable_peanuts = 'AO' then 1 end) ao,
         count(*) total
from     date_test
where    portable_peanuts in ('RO', 'FL', 'LM', 'AO')
group by to_char(session_date, 'yyyy-mm-dd')
order by to_char(session_date, 'yyyy-mm-dd');

CURRENT_DATE         RO         FL         LM         AO      TOTAL
------------ ---------- ---------- ---------- ---------- ----------
2012-02-01            2          2          0          3          7
2012-02-02            2          1          3          2          8

并且,根据要求,这里是上面两个没有示例数据的查询:

select to_char(session_date, 'yyyy-mm-dd') current_date,
       ro,
       fl,
       lm,
       ao,
       ro + fl + lm + ao total
from   date_test
pivot (count(portable_peanuts)
       for portable_peanuts in ('RO' as ro, 'FL' as fl, 'LM' as lm, 'AO' as ao))
order by to_char(session_date, 'yyyy-mm-dd');


select   to_char(session_date, 'yyyy-mm-dd') current_date,
         count(case when portable_peanuts = 'RO' then 1 end) ro,
         count(case when portable_peanuts = 'FL' then 1 end) fl,
         count(case when portable_peanuts = 'LM' then 1 end) lm,
         count(case when portable_peanuts = 'AO' then 1 end) ao,
         count(*) total
from     date_test
where    portable_peanuts in ('RO', 'FL', 'LM', 'AO')
group by to_char(session_date, 'yyyy-mm-dd')
order by to_char(session_date, 'yyyy-mm-dd');

N.B。从您的另一个问题中,我看到您将查询两个日期之间的行。

在这种情况下,与其将 DATE 列转换为字符串,不如将其保留为日期,但截断它以便它在天级别分组。

即,不使用 to_char(session_date, 'yyyy-mm-dd'),只需使用 trunc(session_date)

这样,您的 where 子句将是:

where session_date >= to_date(p_start_date, p_date_format)
and   session_date < to_date (p_end_date, p_date_format)

where trunc(session_date) >= to_date(p_start_date, p_date_format)
and   trunc(session_date) < to_date (p_end_date, p_date_format)

取决于您要如何处理 session_date 的时间部分。前者将能够在 session_date 列上使用索引,而如果您希望它使用索引,则需要在 trunc(session_date) 上创建一个基于函数的索引。

您似乎需要条件聚合:

SELECT TO_CHAR(SESSION_DATE, 'yyyy-mm-dd') as CURRENT_DATE,
       SUM(CASE WHEN PORTABLE_PEANUTS = 'RO' THEN 1 ELSE 0 END) as RO,
       SUM(CASE WHEN PORTABLE_PEANUTS = 'FL' THEN 1 ELSE 0 END) as FL,
       SUM(CASE WHEN PORTABLE_PEANUTS = 'LM' THEN 1 ELSE 0 END) as LM,
       SUM(CASE WHEN PORTABLE_PEANUTS = 'AO' THEN 1 ELSE 0 END) as AO,
       SUM(CASE WHEN PORTABLE_PEANUTS IN ('RO', 'FL', 'LM', 'AO') THEN 1 ELSE 0 END) as TOTAL           
FROM CORE.DATE_TEST
GROUP BY TO_CHAR(SESSION_DATE, 'yyyy-mm-dd')

我不确定你还想做什么。这实际上可能会处理整个查询。