1

到目前为止,当涉及到任何 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;

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

我设计的第二个技巧是使报告动态化并显示前六个月,而无需每个月重新编码。我已经使用 bash 脚本和 GNU Date 完成了这项工作,它们通过命令提示符传递变量。

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

我的问题如下:

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

2)我想要实现的目标是不是被认为是好的做法(或者我应该通过数据透视表进行后处理)?

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

4

6 回答 6

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
于 2012-08-08T13:19:06.963 回答
2

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

使用相同的add_months(trunc(sysdate, 'MM'), n)方法,您可以生成与过去六个月中的每个月匹配的文本标签。你可以使用任何你想要的格式,我已经'Mon RR'接近你原来的硬编码列名。每个生成的值都被别名为x_month_1etc.;该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停止它告诉您何时使用替换变量。

然后只需更改ASDem 查询中的别名子句;而不是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
...
于 2012-08-08T14:13:31.663 回答
1

您可以使用 group by 将狗分组为月份和年份:

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

会得到你今年一月到七月出生的狗的数量

于 2012-08-08T13:19:00.787 回答
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
于 2012-08-08T13:19:50.020 回答
0

在 Oracle 中,有一个常用的习语来表示这样的枢轴,通常涉及SUM(DECODE(...)))or 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;

我希望这有帮助。

于 2012-08-08T13:17:51.457 回答
0

看一下PIVOT

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

EXTRACTTO_CHAR过滤您的日期

于 2012-08-08T13:18:35.073 回答