A simple technique that I see few developers use is the inner outer query to present a final solution to a user. In normal programming you don’t care about extraneous columns as they won’t be presented to the user however when generating reports from a database using raw SQL it’s a timesaver to not have to go back and delete columns.
Here’s an example:
select alertstr from (
select rejdate || ' : ' ||
to_char(round((sum(case when rejects is null then 0 else rejects end)/sum(totals)),2),990.99) ||
'% rejected' as alertstr, round(sum(rejects)/sum(totals),2) as rejmodel from (
select to_char(dtent,'MM/dd') as rejdate, count(1) as totals, null as rejects
from activity
where dtent between to_date('12/01/2007','MM/dd/yyyy') and
to_date('12/10/2007','MM/dd/yyyy') and ttype in ('E','F')
and customer = 1234 group by dtent
union all
select to_char(dtent,'MM/dd'), null, count(1)
from activity
where dtent between to_date('12/01/2007','MM/dd/yyyy') and
to_date('12/10/2007','MM/dd/yyyy') and ttype in ('E','F')
and customer = 1234 and ptype='E' group by dtent
) group by rejdate
) where rejmodel >= 10 and rejmodel < 20
* Note – This is an actual production query so I replaced the table names, id’s and columns with fake data.
You’re probably looking at this query and saying to yourself what a mess. But let’s look at the requirements.
- The query must only return one column and one row only. The row results will be directly display to the user without any programmatic interfernece.
- The query must show all rejections on an arbitrary data span.
- The query will potentially be parsing on database tables that contain upwards of 50-100 million rows of data.
This is a unique situation in that you are displaying query results to the user without any further business logic but it happens occasionally. In hindsight given enough time and concrete requirements I would have redesigned this feature set as the logic is far too encapsulated.
Examining the query the sequence of events occurs as:
- Pull data from the activity table and union this data on a second set from the same table. Notice the columns, the first query is pulling totals and the second query in the union is pulling rejects. When performing a union there is no reason to alias the columns after the first query.
- Since we’ve aliased the columns in the inner query from step 1, the outer query call pull this directly. Select rejects pulls the third column from the inner query. Aliasing is vital when working with inner/outer queries and is really the only way when aggregating data on the fly. The second to last outer query concatenates data such as:
select rejdate || ' : ' || to_char(round((sum(case when rejects is null then 0 else rejects end)/sum(totals)),2),990.99) || '% rejected' as alertstr
- In the final outer query instead of saying :
select rejdate || ' : ' || to_char(round((sum(case when rejects is null then 0 else rejects end)/sum(totals)),2),990.99) || '% rejected'
we just have to say
select alertstr
So now all this aliasing makes sense now. This is also useful since in the where clause we are going to constrain based on the second query which is performing a round function.
If you break down what is occurring in this query you can see the power and flexibility that inner/outer aliasing provides. It’s important to be able to visualize your data sets in your mind and you’ll be all set.

