Counting number of cases within a case - sql

I'm looking to count the number of people who spent a certain amount at a certain store in a certain year.
I tried making a nested CASE expression, the first that looks at transactions at a certain year, the second looking at the sum of a person's transaction to a certain store, but Oracle didn't like nested CASE expressions (unless I was doing it wrong).
From this SQL example:
CREATE TABLE table_name ( PersonId, StoreId, AmountSpent, Year) as
select 1, 1, 60, 2017 from dual union
select 1, 1, 50, 2017 from dual union
select 1, 2, 70, 2018 from dual union
select 2, 1, 10, 2017 from dual union
select 2, 1, 10, 2017 from dual union
select 2, 1, 200, 2018 from dual union
select 2, 2, 60, 2018 from dual union
select 2, 2, 60, 2018 from dual union
select 3, 1, 25, 2017 from dual union
select 3, 2, 200, 2017 from dual union
select 3, 2, 200, 2018 from dual;
Select
StoreId,
SUM(CASE WHEN Year = '2017'
THEN AmountSpent
ELSE 0
End) Year17,
SUM(CASE WHEN Year = '2018'
THEN AmountSpent
ELSE 0
End) Year18
FROM table_name
GROUP BY StoreId;
STOREID YEAR17 YEAR18
---------- ---------- ----------
1 145 200
2 200 330
Would someone be able to make an output like this? I think some numbers might be wrong, but it seems like most of the people get the gist of where I'm going.
+----+---------+------------+---------------------+-------------------+---------------------+---------------------+------------+-------------------+---------------------+
| | STOREID | Y17_INCOME | Y17_SPENT_BELOW_100 | Y17_SPENT_100_150 | Y17_SPENT_ABOVE_150 | Y18_INCOME | Y18_SPENT_BELOW_100 | Y18_SPENT_100_150 | Y18_SPENT_ABOVE_150 |
+----+---------+------------+---------------------+-------------------+---------------------+------------+---------------------+-------------------+---------------------+
| 1 | 1 | 145 | 2 | 1 | 0 | 200 | 2 | 0 | 1 |
+----+---------+------------+---------------------+-------------------+---------------------+---------------------+------------+-------------------+---------------------+
| 2 | 2 | 200 | 0 | 0 | 1 | 330 | 0 | 0 | 1 |
+----+---------+------------+---------------------+-------------------+---------------------+------------+---------------------+-------------------+---------------------+
Not sure if this is possible, but it would be great if so!

I don't think your data matches your expected output.
You need a subquery where you sum spendings of each person yearly for each store ( grouping by store & person). Then you will need to group only by store and use sum to retrieve income for given year. count + case + distinct is the key to count unique person ids that have spent money and assign them to amount spending group (one of three columns for each year).
Notice that yearXX_spending column already holds sum of amount spent by a person for a given store and year (and this is coming from subquery):
select
storeid
, sum(year17_spending) as y17_income
, count(distinct case when year17_spending < 100 then personid end) as y17_spent_below_100
, count(distinct case when year17_spending between 100 and 150 then personid end) as y17_spent_100_150
, count(distinct case when year17_spending > 150 then personid end) as y17_spent_above_150
, sum(year18_spending) as y18_income
, count(distinct case when year18_spending < 100 then personid end) as y18_spent_below_100
, count(distinct case when year18_spending between 100 and 150 then personid end) as y18_spent_100_150
, count(distinct case when year18_spending > 150 then personid end) as y18_spent_above_150
from (
select
storeid
, personid
, sum(case when year = 2017 then amountspent end) as year17_spending
, sum(case when year = 2018 then amountspent end) as year18_spending
from table_name
group by storeid, personid
) t
group by storeid
Output for your sample data:
+----+---------+------------+---------------------+-------------------+---------------------+---------------------+------------+-------------------+---------------------+
| | STOREID | Y17_INCOME | Y17_SPENT_BELOW_100 | Y17_SPENT_100_150 | Y17_SPENT_ABOVE_150 | Y18_INCOME | Y18_SPENT_BELOW_100 | Y18_SPENT_100_150 | Y18_SPENT_ABOVE_150 |
+----+---------+------------+---------------------+-------------------+---------------------+------------+---------------------+-------------------+---------------------+
| 1 | 1 | 145 | 2 | 1 | 0 | 200 | 2 | 0 | 1 |
+----+---------+------------+---------------------+-------------------+---------------------+---------------------+------------+-------------------+---------------------+
| 2 | 2 | 200 | 0 | 0 | 1 | 330 | 0 | 0 | 1 |
+----+---------+------------+---------------------+-------------------+---------------------+------------+---------------------+-------------------+---------------------+
Also note that by doing UNION when you are building sample data, you're getting rid of duplicates so that line:
select 2, 1, 10, 2017
goes to the table only once (not twice). If you mean to include everything without removing duplicates, use UNION ALL instead.

You can do store/person totals in an inline view, which on its own produces:
SELECT
StoreId,
PersonId,
Year,
SUM(AmountSpent) AS TotalSpent
FROM table_name
GROUP BY
StoreId,
PersonId,
Year
ORDER BY
StoreId,
Year,
PersonId;
STOREID PERSONID YEAR TOTALSPENT
---------- ---------- ---------- ----------
1 1 2017 110
1 2 2017 10
1 3 2017 25
1 2 2018 200
2 3 2017 200
2 1 2018 70
2 2 2018 60
2 3 2018 200
and then use multiple separate case expressions and aggregates in an outer query:
SELECT
StoreId,
SUM(CASE WHEN Year = '2017'
THEN TotalSpent
ELSE 0
END) Year17,
COUNT(CASE WHEN Year = '2017'
AND TotalSpent < 100
THEN TotalSpent
END) AS Year17lt100,
COUNT(CASE WHEN Year = '2017'
AND TotalSpent >= 100
AND TotalSpent < 150
THEN TotalSpent
END) AS Year17gte100lt150,
COUNT(CASE WHEN Year = '2017'
AND TotalSpent >= 150
THEN TotalSpent
END) AS Year17gte150,
SUM(CASE WHEN Year = '2018'
THEN TotalSpent
ELSE 0
END) Year18,
COUNT(CASE WHEN Year = '2018'
AND TotalSpent < 100
THEN TotalSpent
END) AS Year18lt100,
COUNT(CASE WHEN Year = '2018'
AND TotalSpent >= 100
AND TotalSpent < 150
THEN TotalSpent
END) AS Year18gte100lt150,
COUNT(CASE WHEN Year = '2017'
AND TotalSpent >= 150
THEN TotalSpent
END) AS Year18gte150
FROM (
SELECT
StoreId,
PersonId,
Year,
SUM(AmountSpent) AS TotalSpent
FROM table_name
GROUP BY
StoreId,
PersonId,
Year
)
GROUP BY StoreId
ORDER BY StoreId;
which gets:
STOREID YEAR17 YEAR17LT100 YEAR17GTE100LT150 YEAR17GTE150 YEAR18 YEAR18LT100 YEAR18GTE100LT150 YEAR18GTE150
---------- ---------- ----------- ----------------- ------------ ---------- ----------- ----------------- ------------
1 145 2 1 0 200 0 0 0
2 200 0 0 1 330 2 0 1
That doesn't match the output in your image, but I think that's wrong...
I've also changed the buckets slightly from what your column headings suggested, but you can tweak those if you do really want what you showed.
You could also reduce the code and repetition by using a pivot() clause, if you're on a version of Oracle which supports that; which can provide multiple outputs and allows conditional aggregation too:
SELECT *
FROM (
SELECT
StoreId,
Year,
SUM(AmountSpent) AS TotalSpent
FROM table_name
GROUP BY
StoreId,
PersonId,
Year
)
PIVOT (
SUM(TotalSpent) as total,
COUNT(CASE WHEN TotalSpent < 100 THEN TotalSpent END) as lt100,
COUNT(CASE WHEN TotalSpent >= 100 AND TotalSpent < 150 THEN TotalSpent END) as gte100lt150,
COUNT(CASE WHEN TotalSpent >= 150 THEN TotalSpent END) as gte150
FOR Year IN (2017 as year17, 2018 as year18)
)
ORDER BY StoreId;
STOREID YEAR17_TOTAL YEAR17_LT100 YEAR17_GTE100LT150 YEAR17_GTE150 YEAR18_TOTAL YEAR18_LT100 YEAR18_GTE100LT150 YEAR18_GTE150
---------- ------------ ------------ ------------------ ------------- ------------ ------------ ------------------ -------------
1 145 2 1 0 200 0 0 1
2 200 0 0 1 330 2 0 1
Oracle will expand that under the hood to look like the original longer query above, but it's easier to maintain.
Updated SQL Fiddle.

Related

SQL/DB2 getting single row of results per employee with a UNION

I'm currently using a UNION on 2 select statements and while I'm getting the correct data, it's not exactly what I actually need when it comes time to use it in a front-end view
I'm currently using this query:
SELECT
T.employee as employee,
'Orders' as TYPE,
SUM(CASE WHEN t.order_count < QUANT THEN t.order_count ELSE QUANT END) as DATA
FROM schemaOne.order_list T
WHERE t.order_date > CURRENT_DATE - 35 DAYS
group by t.employee
UNION
select
T.employee as employee,
'Sales' as TYPE,
sum(price * quant) as DATA
from schemaOne.sales T
WHERE T.sales_date > CURRENT_DATE - 35 DAYS
group by T.employee
order by data desc;
with these dummy tables as examples and getting the following result:
order_list
employee | order_count | quant | order_date
--------------------------------------------------
123 | 5 | 1 | 2022-03-02
456 | 1 | 5 | 2022-03-02
sales
employee | price | quant | order_date
--------------------------------------------------
123 | 500 | 1 | 2022-03-02
456 | 1000 | 1 | 2022-03-02
Result
employee | type | data
------------------------------------------
123 Orders 1
123 Sales 500
456 Orders 5
456 Sales 1000
Is there a way to use a UNION but alter it so that I can instead get a single row for each employee and just get rid of the type/data columns and instead set each piece of data to the desired column (the type would instead be the column name ) like so:
Desired Result
employee | Orders | Sales
---------------------------------
123 | 1 | 500
456 | 5 | 1000
Try adding an outer query:
select employee,
MAX(case when type=Orders then data end) as orders ,
MAX(case when type=Sales then data end) as Sales
from (
SELECT T.employee as employee,
'Orders' as TYPE,
SUM(CASE WHEN t.order_count < QUANT THEN t.order_count ELSE QUANT END) as DATA
FROM schemaOne.order_list T
WHERE t.order_date > CURRENT_DATE - 35 DAYS
group by t.employee
UNION
select T.employee as employee,
'Sales' as TYPE,
sum(price * quant) as DATA
from schemaOne.sales T
WHERE T.sales_date > CURRENT_DATE - 35 DAYS
group by T.employee
) as t1
GROUP BY employee;
Note that I removed order by data desc it has no effect inside the union
You can join tables through employee columns such as
SELECT o.employee,
SUM(CASE
WHEN o.order_count < o.quant THEN
o.order_count
ELSE
o.quant
END) AS Orders,
SUM(s.price * s.quant) AS Sales
FROM schemaOne.order_list o
JOIN schemaOne.sales s
ON s.employee = o.employee
AND s.sales_date = o.order_date
WHERE o.order_date > current_date - 35 DAYS
GROUP BY o.employee

How do i compare year on change in a SQL database without hardcoding the years?

I have a database with schema such as:
----------------------------------------------
SalesID | ProductID | ProductName | SalesDate
----------------------------------------------
1 | 1 | Football | 2020-01-07
2 | 1 | Football | 2019-01-08
3 | 1 | Football | 2019-01-08
4 | 1 | Football | 2019-01-08
5 | 2 | Racket | 2020-01-07
6 | 2 | Racket | 2018-01-07
7 | 2 | Racket | 2018-01-07
----------------------------------------------
What i want to do is to dynamically retrieve the total year on year change in the amount of sales for each year and its previous year. But in the case where there was no sales for a certain product in one year, then compare it to the next available one.
For example, the "Football" product had 3 sales in 2019 and 1 sale in 2020. So that is a decrease of 66%. And i want to print that.
The "Racket" product had 1 sale in 2020, none in 2019 but 2 in 2018. So i want to print it such that it had a decrease of 50%.
I would like to do this without hardcoding the years using a CASE WHEN statement.
My attempt can get the data i want for each year but if there is year that has no data it compares it anyway to it, instead of comparing it to the next year that contains sales data
declare #maxYear int
declare #minYear int
declare #saleChange int
set #maxYear = (select max(year(SalesDate)) from Products)
set #minYear = (select min(year(SalesDate)) from Products)
create table #temp(
ProductName varchar(255),
CurrentYear int,
CurrentSalesCount int,
PrevYear int,
PrevYrSalesCount int,
Growth int)
WHILE (#maxYear >= #minYear)
BEGIN
insert into #temp
select
d.ProductName,
CurrentYear = #maxYear,
CurrentSalesCount = sum(case when year(d.SalesDate) = #maxYear then 1 else null
end),
PrevYear = #maxYear - 1,
PrevYrSalesCount = sum(case when year(d.SalesDate) = #maxYear - 1 then 1 else null
end),
Growth = (sum(case when year(d.SalesDate) = #maxYear then 1 else null end)
-sum(case when year(d.SalesDate) = #maxYear - 1 then 1 else null end))
from Products d
group by
d.ProductName
Set #maxYear = #maxYear - 1
END
select * from #temp
You may try below query, tested at dbfiddle
SELECT
pid as ProductId,
salyr as SaleYear,
cnt as SaleCount,
LAG(cnt, 1, 0) OVER (PARTITION BY pid ORDER BY salyr) AS PrevYearSale,
cnt - LAG(cnt, 1, 0) OVER (PARTITION BY pid ORDER BY salyr) AS DiffPrevYear,
case when LAG(cnt, 1, 0) OVER (PARTITION BY pid ORDER BY salyr) = 0 then 0
else
round((cnt - LAG(cnt, 1, 0) OVER (PARTITION BY pid ORDER BY salyr))*100
/(LAG(cnt, 1, 0) OVER (PARTITION BY pid ORDER BY salyr)),2) end as PerDifference
FROM
(Select productid as pid,
extract(year from salesdate) as salyr,
count(1) as cnt
from test
group by productid, extract(year from salesdate)
order by 1, 2)
;
Use aggregation and window functions:
select year(salesdate), product, count(*) as cnt
lag(count(*)) over (partition by product order by year(salesdate)) as prev_cnt,
(count(*) * 1.0 /
lag(count(*)) over (partition by product order by year(salesdate))
) as ratio
from t
group by year(salesdate);

Count records by Grouping a period

I have a table that stores per day if a user worked or if he was on vacation based on a value.
Example table, Value = 1 -> WorkDay, Value = 2 -> Vacation:
User | Day | Value
--------|------------|-------
user-1 | 2021-01-01 | 1
user-1 | 2021-01-02 | 1
user-1 | 2021-01-03 | 1
user-1 | 2021-01-04 | 1
user-1 | 2021-01-05 | 2
user-1 | 2021-01-06 | 2
...
I'll like to convert this table to this (Using the simple example above):
User | Year | Month | WorkDay | Vacation
--------|------|-------|---------|---------
user-1 | 2021 | 01 | 4 | 2
...
I tried using group by, subqueries and case, but the whole thing become a mess.
SELECT
YEAR(Day),
MONTH(DAY),
User,
...
From Table1
Group By YEAR(Day), MONTH(DAY), User
Just use conditional aggregation:
select year(day), month(day),
sum(case when value = 1 then 1 else 0 end) as workday,
sum(case when value = 2 then 1 else 0 end) as vacation
from table1
group by year(day), month(day)
You can use conditional aggregation as below:
select User,
year(day),
month(day),
sum(case when value = 1 then 1 else 0 end) as WorkDay,
sum(case when value = 2 then 1 else 0 end) as Vacation
from table1
group by User, year(day), month(day)
DB-Fiddle:
Schema and insert statements:
create table table1([User] varchar(50), Day date, Value int);
insert into table1 values('user-1' , '2021-01-01' , 1);
insert into table1 values('user-1' , '2021-01-02' , 1);
insert into table1 values('user-1' , '2021-01-03' , 1);
insert into table1 values('user-1' , '2021-01-04' , 1);
insert into table1 values('user-1' , '2021-01-05' , 2);
insert into table1 values('user-1' , '2021-01-06' , 2);
Query:
select [User] as "user",
year(day) as "year",
month(day) as "month",
sum(case when value = 1 then 1 else 0 end) as WorkDay,
sum(case when value = 2 then 1 else 0 end) as Vacation
from table1
group by [User], year(day), month(day)
GO
Output:
user
year
month
WorkDay
Vacation
user-1
2021
1
4
2
db<fiddle here

Count and pivot a table by date

I would like to identify the returning customers from an Oracle(11g) table like this:
CustID | Date
-------|----------
XC321 | 2016-04-28
AV626 | 2016-05-18
DX970 | 2016-06-23
XC321 | 2016-05-28
XC321 | 2016-06-02
So I can see which customers returned within various windows, for example within 10, 20, 30, 40 or 50 days. For example:
CustID | 10_day | 20_day | 30_day | 40_day | 50_day
-------|--------|--------|--------|--------|--------
XC321 | | | 1 | |
XC321 | | | | 1 |
I would even accept a result like this:
CustID | Date | days_from_last_visit
-------|------------|---------------------
XC321 | 2016-05-28 | 30
XC321 | 2016-06-02 | 5
I guess it would use a partition by windowing clause with unbounded following and preceding clauses... but I cannot find any suitable examples.
Any ideas...?
Thanks
No need for window functions here, you can simply do it with conditional aggregation using CASE EXPRESSION :
SELECT t.custID,
COUNT(CASE WHEN (last_visit- t.date) <= 10 THEN 1 END) as 10_day,
COUNT(CASE WHEN (last_visit- t.date) between 11 and 20 THEN 1 END) as 20_day,
COUNT(CASE WHEN (last_visit- t.date) between 21 and 30 THEN 1 END) as 30_day,
.....
FROM (SELECT s.custID,
LEAD(s.date) OVER(PARTITION BY s.custID ORDER BY s.date DESC) as last_visit
FROM YourTable s) t
GROUP BY t.custID
Oracle Setup:
CREATE TABLE customers ( CustID, Activity_Date ) AS
SELECT 'XC321', DATE '2016-04-28' FROM DUAL UNION ALL
SELECT 'AV626', DATE '2016-05-18' FROM DUAL UNION ALL
SELECT 'DX970', DATE '2016-06-23' FROM DUAL UNION ALL
SELECT 'XC321', DATE '2016-05-28' FROM DUAL UNION ALL
SELECT 'XC321', DATE '2016-06-02' FROM DUAL;
Query:
SELECT *
FROM (
SELECT CustID,
Activity_Date AS First_Date,
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '10' DAY FOLLOWING )
- 1 AS "10_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '20' DAY FOLLOWING )
- 1 AS "20_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '30' DAY FOLLOWING )
- 1 AS "30_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '40' DAY FOLLOWING )
- 1 AS "40_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '50' DAY FOLLOWING )
- 1 AS "50_Day",
ROW_NUMBER() OVER ( PARTITION BY CustID ORDER BY Activity_Date ) AS rn
FROM Customers
)
WHERE rn = 1;
Output
USTID FIRST_DATE 10_Day 20_Day 30_Day 40_Day 50_Day RN
------ ------------------- ---------- ---------- ---------- ---------- ---------- ----------
AV626 2016-05-18 00:00:00 0 0 0 0 0 1
DX970 2016-06-23 00:00:00 0 0 0 0 0 1
XC321 2016-04-28 00:00:00 0 0 1 2 2 1
Here is an answer that works for me, I have based it on your answers above, thanks for contributions from MT0 and Sagi:
SELECT CustID,
visit_date,
Prev_Visit ,
COUNT( CASE WHEN (Days_between_visits) <=10 THEN 1 END) AS "0-10_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 11 AND 20 THEN 1 END) AS "11-20_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 21 AND 30 THEN 1 END) AS "21-30_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 31 AND 40 THEN 1 END) AS "31-40_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 41 AND 50 THEN 1 END) AS "41-50_day" ,
COUNT( CASE WHEN (Days_between_visits) >50 THEN 1 END) AS "51+_day"
FROM
(SELECT CustID,
visit_date,
Lead(T1.visit_date) over (partition BY T1.CustID order by T1.visit_date DESC) AS Prev_visit,
visit_date - Lead(T1.visit_date) over (
partition BY T1.CustID order by T1.visit_date DESC) AS Days_between_visits
FROM T1
) T2
WHERE Days_between_visits >0
GROUP BY T2.CustID ,
T2.visit_date ,
T2.Prev_visit ,
T2.Days_between_visits;
This returns:
CUSTID | VISIT_DATE | PREV_VISIT | DAYS_BETWEEN_VISIT | 0-10_DAY | 11-20_DAY | 21-30_DAY | 31-40_DAY | 41-50_DAY | 51+DAY
XC321 | 2016-05-28 | 2016-04-28 | 30 | | | 1 | | |
XC321 | 2016-06-02 | 2016-05-28 | 5 | 1 | | | | |

Get collection of integers from counter

Which would be the best way to get the number of people hired on each day of the week for 7 years from a table People that has their entry_date with a day-month-year as 01-Jun-91.
For example:
2000 2001 2002 etc..
SUN 2 0 1
MON 0 0 2
Do I have to create a counter for each day of each year? Like Sun2000, Sun2001 etc?
You need to join each day of the week with your entry_date and pivot the results.
SQL Fiddle
Query:
with x(days) as (
select 'sunday' from dual union all
select 'monday' from dual union all
select 'tuesday' from dual union all
select 'wednesday' from dual union all
select 'thursday' from dual union all
select 'friday' from dual union all
select 'saturday' from dual
)
select * from (
select x.days,
extract(year from emp.entry_date) entry_year
from x left outer join emp
on x.days = to_char(emp.entry_date,'fmday')
)
pivot(count(entry_year)
for entry_year in (
2007,
2008,
2009,
2010,
2011,
2012
)
)
order by
case days when 'sunday' then 1
when'monday' then 2
when'tuesday' then 3
when'wednesday' then 4
when'thursday' then 5
when'friday' then 6
when'saturday' then 7
end
Results:
| DAYS | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 |
|-----------|------|------|------|------|------|------|
| sunday | 0 | 0 | 0 | 0 | 0 | 0 |
| monday | 0 | 0 | 0 | 2 | 0 | 0 |
| tuesday | 0 | 0 | 0 | 0 | 1 | 0 |
| wednesday | 0 | 0 | 0 | 1 | 2 | 1 |
| thursday | 0 | 0 | 0 | 0 | 0 | 3 |
| friday | 0 | 0 | 0 | 0 | 0 | 0 |
| saturday | 0 | 0 | 0 | 0 | 0 | 0 |
You need to use group by on the year and entry_date to get the count of employees joined for each date.
For example:
Rem -- Assuming following table structure
create table People(id number, name varchar2(20), entry_date date);
Rem -- Following groups the results
select extract(year from entry_date) "Year", entry_date, count(id)
from People
where extract(year from entry_date) between 2008 and 2015
group by extract(year from entry_date), entry_date
order by extract(year from entry_date), entry_date;
Check this sqlfiddle to explore more.
Depending on the version of Oracle you're using (10g doesn't have the PIVOT function, for example), you might try something like the following conditional aggregation:
SELECT day_abbrev
, SUM(CASE WHEN year_num = 2000 THEN person_cnt ELSE 0 END) AS "2000"
, SUM(CASE WHEN year_num = 2001 THEN person_cnt ELSE 0 END) AS "2001"
, SUM(CASE WHEN year_num = 2002 THEN person_cnt ELSE 0 END) AS "2002"
, SUM(CASE WHEN year_num = 2003 THEN person_cnt ELSE 0 END) AS "2003"
, SUM(CASE WHEN year_num = 2004 THEN person_cnt ELSE 0 END) AS "2004"
, SUM(CASE WHEN year_num = 2005 THEN person_cnt ELSE 0 END) AS "2005"
, SUM(CASE WHEN year_num = 2006 THEN person_cnt ELSE 0 END) AS "2006"
FROM (
SELECT TO_CHAR(entry_date, 'DY') AS day_abbrev
, EXTRACT(YEAR FROM entry_date) AS year_num
, COUNT(*) AS person_cnt
FROM people
GROUP BY TO_CHAR(entry_date, 'DY'), EXTRACT(YEAR FROM entry_date)
) GROUP BY day_abbrev
ORDER BY TO_CHAR(NEXT_DAY(SYSDATE, day_abbrev), 'D');