views:

406

answers:

3

I'm SELECTing some aggregate data and grouping on the date and a particular field. I want to display all values in that field and a count for those values even if there was no data matching that field on that day. E.g.

Date        MyField  Count
2009-09-25  A        2
2009-09-25  B        0
2009-09-24  A        1
2009-09-24  B        1

The Oracle SQL I currently have to do this is akin to the following:

SELECT today,
       mytable.myfield,
       COUNT(
         CASE WHEN fields.myfield = mytable.myfield AND
                   date >= today AND
                   date < tomorrow
              THEN 1
         END
       )
FROM (
       SELECT TRUNC(SYSDATE) + 1 - LEVEL AS today,
              TRUNC(SYSDATE) + 2 - LEVEL AS tomorrow
       FROM DUAL
       CONNECT BY LEVEL <= 30
     ),
     (
       /* This is the part that seems inefficient */
       SELECT DISTINCT myfield
       FROM mytable
       WHERE myfield IN ('A', 'B')
     ) fields,
     mytable
GROUP BY today, mytable.myfield
ORDER BY today DESC, mytable.myfield ASC

My concern is that I know exactly which values I want to display for myfield, and it seems inefficient to have a SELECT query that accesses mytable. I was wondering if there's some way I could do something like this in that sub-query:

SELECT ('A', 'B') AS myfield
FROM DUAL

I'm using an older version of Oracle where WITH clauses do not work.

+1  A: 

You would have to get them as different rows, not different columns. So you'll end up with

select 'A' from dual
union
select 'B' from dual

In that case, the query should be equivalent as long as there are rows in mytable with fields 'A' and 'B'. If ever there aren't, then your subquery will return rows that the original subquery would not.

jbourque
A: 

Why don't you upgrade your Oracle Version? The with-clause is added first to Oracle 9.2 (2002). Are you still using Oracle 8?

tuinstoel
A: 

You don't have a join between the FIELDS sub-query and MYTABLE, so your resultset will contain a row for every value of MYFIELD for the last thirty days.

However, rather than adding that join, why not ditch the sub-query and just filter on MYTABLE.MYFIELD? Also, if you are concerned about performance you should bound the date in a WHERE clause, otherwise you will process every row in MYTABLE.

select today
       , myfield
       , count ( case when trunc(somedate) = today then 1 end ) as ab_count
from   ( select trunc(sysdate) + 1 - level as today
         from dual
         connect by level <= 30 )
       , mytable
where myfield in ('A', 'B')
and somedate >= trunc(sysdate) - 30
group by today, myfield
order by today desc, myfield asc
/

edit

I have run your original query and my revised one against some test data. You will just have to take my word for it that the two resulsets were in fact identical - or try it yourself :)

Your query returns:

TODAY       M   AB_COUNT
----------- - ----------
26-SEP-2009 A          0
26-SEP-2009 B          0
25-SEP-2009 A          2
25-SEP-2009 B          2
24-SEP-2009 A          2
24-SEP-2009 B          0
...
29-AUG-2009 A          1
29-AUG-2009 B          2
28-AUG-2009 A          1
28-AUG-2009 B          0

60 rows selected.

SQL>

My query returns:

TODAY       M   AB_COUNT
----------- - ----------
26-SEP-2009 A          0
26-SEP-2009 B          0
25-SEP-2009 A          2
25-SEP-2009 B          2
24-SEP-2009 A          2
24-SEP-2009 B          0
...
29-AUG-2009 A          1
29-AUG-2009 B          2
28-AUG-2009 A          1
28-AUG-2009 B          0

60 rows selected.

SQL>
APC