使用union函数生成新列

时间:2012-08-08 13:10:00

标签: sql oracle report

到目前为止,我在任何sql上都是自学成才,教你自己的问题是,虽然你经常修复,但它可能不是实现目标的最佳实践。以下代码是其中一个黑客:

select breed, sum(mar_11) as mar_11, sum(apr_11) as apr_11
from (
  select d.breed, d.qty as mar_11, 0 as apr_11
  from dogs d
  where d.entry_date between '01-MAR-11' and '31-MAR-11'
  union all
  select d.breed, 0 as mar_11, d.qty as apr_11
  from dogs d
  where d.entry_date between '01-APR-11' and '30-APR-11'
) t
group by breed;

上述代码的完成目标是提供一个格式化的报告,显示3月和4月出生的狗总数。当我们处理几个时间段时,这很好,但当你进入20-30个不同的时间段时,代码变得乏味和重复。

我设计的第二个黑客是使报告充满活力并显示前六个月而不必每个月重新编码。我用bash脚本和GNU Date完成了这个,它通过命令提示符传递变量。

(e.g.)
sqlplus64 ilove/dogs@dogville @myreport var1 var2 var3 var4

我的问题如下:

1)我可以使用什么工具在我的sql语句中更有效率(即,通过更改日期来减少重复相同的代码)?

2)我正在努力实现甚至考虑好的做法(或者我应该通过数据透视表进行后期处理)?

3)如何在不需要bash和GNU Date的情况下使我的sql语句动态化?

6 个答案:

答案 0 :(得分:2)

select
  breed,
  SUM(CASE WHEN entry_date BETWEEN '01-Mar-11' AND '31-Mar-11' THEN qty ELSE 0 END) AS mar_11,
  SUM(CASE WHEN entry_date BETWEEN '01-APR-11' AND '30-APR-11' THEN qty ELSE 0 END) AS apr_11
FROM
  dogs
WHERE
  entry_date BETWEEN '01-Mar-11' AND '30-Apr-11'
GROUP BY
  breed

编辑:本月和前5个月

select
  breed,
  SUM(CASE WHEN entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -5)
            AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -4)
           THEN qty ELSE 0 END)                                      AS month_1,
  SUM(CASE WHEN entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -4)
            AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -3)
           THEN qty ELSE 0 END)                                      AS month_2,
  SUM(CASE WHEN entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -3)
            AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -2)
           THEN qty ELSE 0 END)                                      AS month_3,
  SUM(CASE WHEN entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -2)
            AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1)
           THEN qty ELSE 0 END)                                      AS month_4,
  SUM(CASE WHEN entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -1)
            AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'),  0)
           THEN qty ELSE 0 END)                                      AS month_5,
  SUM(CASE WHEN entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'),  0)
            AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'),  1)
           THEN qty ELSE 0 END)                                      AS month_6
FROM
  dogs
WHERE
      entry_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -5)
  AND entry_date <  ADD_MONTHS(TRUNC(SYSDATE, 'MM'),  1)
GROUP BY
  breed

答案 1 :(得分:2)

作为一个次要(如果不是轻浮)的@Dems六个月跨度查询(对于评论来说太大)的补充,因为你使用的是SQL * Plus,你可以使用它的new_value子句和{{ 3}}设置列标题以匹配月份。

使用相同的add_months(trunc(sysdate, 'MM'), n)方法,您可以生成与过去六个月中的每一个匹配的文本标签。你可以使用你想要的任何格式,我已经'Mon RR'接近你原来的硬编码列名。每个生成的值都别名为x_month_1等; column ... new_value ...子句在脚本中稍后将其作为替换变量使用,您可以将其引用为&y_month_1

column x_month_1 new_value y_month_1;
column x_month_2 new_value y_month_2;
column x_month_3 new_value y_month_3;
column x_month_4 new_value y_month_4;
column x_month_5 new_value y_month_5;
column x_month_6 new_value y_month_6;

set termout off

select to_char(add_months(trunc(sysdate, 'MM'), -5), 'Mon RR') as x_month_1,
       to_char(add_months(trunc(sysdate, 'MM'), -4), 'Mon RR') as x_month_2,
       to_char(add_months(trunc(sysdate, 'MM'), -3), 'Mon RR') as x_month_3,
       to_char(add_months(trunc(sysdate, 'MM'), -2), 'Mon RR') as x_month_4,
       to_char(add_months(trunc(sysdate, 'MM'), -1), 'Mon RR') as x_month_5,
       to_char(trunc(sysdate, 'MM'), 'Mon RR') as x_month_6
from dual;

set termout on
set verify off

termout off / on会在输出中停止显示,verify off会在它使用替换变量时停止告诉您。

然后只需更改Dem查询中的AS别名子句;而不是AS month_1使用AS "&y_month_1"。双引号是必需的,因为我选择了一个带有空格的日期格式;这会阻止它导致错误。

或者,如果您希望将查询与查询分开,请保留别名并使用另一个column子句来实现此目的:

column month_6 heading "&y_month_6"

无论哪种方式,你都会得到类似的东西:

BREED          Mar 12     Apr 12     May 12     Jun 12     Jul 12     Aug 12
---------- ---------- ---------- ---------- ---------- ---------- ----------
Beagle              1          4          0          0          0          0
...

答案 2 :(得分:1)

您可以使用分组将狗分组为数月和数年:

SELECT 
    breed, EXTRACT(MONTH FROM entry_date) AS month, EXTRACT(YEAR FROM entry_date) AS year, sum(qty) AS number_of_dogs
FROM
    dogs
GROUP BY 
    breed, EXTRACT(MONTH FROM entry_date), EXTRACT(YEAR FROM entry_date)

然后,您可以添加逻辑以将结果过滤到特定时间范围。例如:

SELECT *
FROM
(
    SELECT 
        breed, EXTRACT(MONTH FROM entry_date) AS month, EXTRACT(YEAR FROM entry_date) AS year, sum(qty) AS number_of_dogs
    FROM
        dogs
    GROUP BY 
        breed, EXTRACT(MONTH FROM entry_date), EXTRACT(YEAR FROM entry_date)
)
WHERE
    year = 2012 AND month < 8

在今年1月的几个月里,你会得到多少只出生的狗

答案 3 :(得分:1)

我认为你做了什么:

select to_char(d.entry_date, 'YYYY-MM') as yyyymm, breed,
       sum(d_qty)
from dogs d
group by to_char(d.entry_date, 'YYYY-MM'), breed
order by 1, 2

这会为每个月和品种创建一个单独的行。如果你需要在一行中获取这些,那么你需要PIVOT语法,或在Excel中进行透视。

很有可能在Excel中进行旋转就足够了。您也可以通过明确的方式进行转动(正如我认为其他响应者建议的那样):

select breed,
       sum(case when to_char(d.entry_date, 'YYYY-MM') = '2012-06' then d_qty end) as qty_201206,
       sum(case when to_char(d.entry_date, 'YYYY-MM') = '2012-05' then d_qty end) as qty_201205,
       ...
from dogs d
group by breed
order by 1

答案 4 :(得分:0)

在Oracle中,通常使用这样的惯用法,通常涉及SUM(DECODE(...)))SUM(CASE WHEN...THEN...)

select breed
,      sum(decode(TO_CHAR(entry_date,'YYYYMM'),'201103',qty,0) mar_11
,      sum(decode(TO_CHAR(entry_date,'YYYYMM'),'201104',qty,0) apr_11
from dogs d
group by breed;

我希望这会有所帮助。

答案 5 :(得分:0)

查看PIVOT

http://www.orafaq.com/wiki/PIVOT

同时EXTRACTTO_CHAR来过滤您的日期