SAS/SQL sum amounts distinctly for each group by object? - sql

I have the following code:
proc sql;
CREATE TABLE temp AS
(SELECT asofdt,
SUM(CASE WHEN trans_state ='cur_cur' THEN 1 ELSE 0 END) AS _cur_cur,
SUM(CASE WHEN trans_state ='cur_worse' THEN 1 ELSE 0 END) AS _cur_worse,
SUM(CASE WHEN trans_state ='cur_pre' THEN 1 ELSE 0 END) AS _cur_pre,
SUM(CASE WHEN trans_state ='30_better' THEN 1 ELSE 0 END) AS _30_better,
SUM(CASE WHEN trans_state ='30_30' THEN 1 ELSE 0 END) AS _30_30,
SUM(CASE WHEN trans_state ='60_90' THEN 1 ELSE 0 END) AS _60_90
FROM PERFORMANCE_TRANS_STATES_CLEAN
GROUP BY asofdt);
run;
The problem is it is adding the value from the previous group by asofdt onto the next one. So it is a cumulative sum as I go down the group bys. I would like the sum to be specific to each group by object. Any ideas on how?
Here's a picture of my output.

Your program seems fine to me. I reproduced it below with fewer observations and did not find that the total was cumulative.
data df;
input asofdt MMDDYY8. trans_state $;
datalines;
01/01/16 cur_cur
01/02/16 cur_pre
01/02/16 cur_pre
01/02/16 cur_cur
01/03/16 cur_pre
;
run;
proc sql;
CREATE TABLE temp AS
(SELECT asofdt,
SUM(CASE WHEN trans_state ='cur_cur' THEN 1 ELSE 0 END) AS _cur_cur,
SUM(CASE WHEN trans_state ='cur_worse' THEN 1 ELSE 0 END) AS _cur_worse,
SUM(CASE WHEN trans_state ='cur_pre' THEN 1 ELSE 0 END) AS _cur_pre,
SUM(CASE WHEN trans_state ='30_better' THEN 1 ELSE 0 END) AS _30_better,
SUM(CASE WHEN trans_state ='30_30' THEN 1 ELSE 0 END) AS _30_30,
SUM(CASE WHEN trans_state ='60_90' THEN 1 ELSE 0 END) AS _60_90
FROM df
GROUP BY asofdt);
quit;

You might want to check your data, as this query is fine. It is indeed running separately on each ASOFDT. You can check that trivially by comparing a single line with a WHERE (WHERE ASOFDT='01OCT2016'd or WHERE ASOFDT='10/01/2016' depending on the type of that variable).
proc sql;
CREATE TABLE temp AS
(SELECT stock,
SUM(CASE WHEN month(date)=01 THEN 1 ELSE 0 END) AS _jan,
SUM(CASE WHEN month(date)=02 THEN 1 ELSE 0 END) AS _feb,
SUM(CASE WHEN month(date)=03 THEN 1 ELSE 0 END) AS _mar,
SUM(CASE WHEN month(date)=04 THEN 1 ELSE 0 END) AS _apr
FROM sashelp.stocks
GROUP BY stock);
quit;
Nothing about that should be cumulative. Unless your data is cumulative, which it sort of makes sense it would be with "ASOFDT"?

Related

customer count where transactions are not null

with hrk as (
select a.LBrCode,a.VcrAcctId,a.DrCr,a.FcyTrnAmt amt,
b.CustNo,b.AcctType,c.AMLRating x
from vcr a
inner join ac_mast b on a.LBrCode=b.LBrCode and substring(a.VcrAcctId,1,24)=substring(b.PrdAcctId,1,24)
left join cust_mast c on b.CustNo=c.CustNo
where a.entrydate between '01-jan-2021' and '31-mar-2021'
)
select AcctType,
count(*) over() cust_cnt,
sum(case when (DrCr='C' and x=1)then amt else 0 end) low_credit,
sum(case when (DrCr='C' and x=1)then 1 else 0 end) low_credit_cnt,
sum(case when (DrCr='D' and x=1)then amt else 0 end) low_debit,
sum(case when (DrCr='D' and x=1)then 1 else 0 end) low_debit_cnt,
sum(case when (DrCr='C' and x=2)then amt else 0 end) Medium_credit,
sum(case when (DrCr='C' and x=2)then 1 else 0 end) Medium_credit_cnt,
sum(case when (DrCr='D' and x=2)then amt else 0 end) Medium_debit,
sum(case when (DrCr='D' and x=2)then 1 else 0 end) Medium_debit_cnt,
sum(case when (DrCr='C' and x=2)then amt else 0 end) high_credit,
sum(case when (DrCr='C' and x=2)then 1 else 0 end) high_credit_cnt,
sum(case when (DrCr='D' and x=2)then amt else 0 end) high_debit,
sum(case when (DrCr='D' and x=2)then 1 else 0 end) high_debit_cnt
from hrk
group by AcctType
-- There can be N no of transactions for a given custNo and there can be multiple custono under AMLRating
I want No of Custno involved in each category namely low,medium,high where sum is not null
You have count(*) over() with the alias name cust_cnt. A better alias would be acct_type_cnt, because with the OVER clause you count how many rows are in your result, i.e. how many AcctType there are.
If you want to count customers, either use
count(*)
or use
count(custono)
if a customer number can be null
or use
count(distinct custono)
if a customer number can appear multiple times and you want to count it just once.
You can of course apply this on your conditional aggregation expressions, e.g.
count(distinct case when drcr = 'C' and x = 1 then custono end) low_credit_cnt,

Invalid column name 'total_counted'

I'm querying the bin table to get the total active bins, total counted bins and calculate the percent of bins counted. Here's my query:
SELECT bin.location_id
,SUM(CASE WHEN bin.delete_flag = 'N' THEN 1 ELSE 0 END) AS total_active
,SUM(CASE WHEN bin.date_last_counted > 0 THEN 1 ELSE 0 END) AS total_counted
--,(total_counted / total_active) as pct_counted
From bin
Group by bin.location_id
Order by bin.location_id
When I try to use the code to create my pct_counted, it tells me "invalid column name" for both of the columns I'm using to calculate that value. Data looks like below.
location_id total_active total_counted
2 11502 484
6 2281 108
15 1772 253
Can anyone help?
You need to repeat the expressions (or use a subquery or CTE). I would recommend:
SELECT bin.location_id,
SUM(CASE WHEN bin.delete_flag = 'N' THEN 1 ELSE 0 END) AS total_active,
SUM(CASE WHEN bin.date_last_counted > 0 THEN 1 ELSE 0 END) AS total_counted
(SUM(CASE WHEN bin.date_last_counted > 0 THEN 1.0 ELSE 0 END) /
SUM(CASE WHEN bin.delete_flag = 'N' THEN 1 END)
) AS as pct_counted
FROM bin
GROUP BY bin.location_id
ORDER BY bin.location_id;
Note that I removed the ELSE clause for the second expression. This avoids divide-by-zero. The 1.0 also ensures decimal division even if your database does integer division.
I would format the sub-query this way:
Select
a.location_id
total_counted/total_active as pct_counted
from(select
bin.location_id
,SUM(CASE WHEN bin.delete_flag = 'N' THEN 1 ELSE 0 END) AS total_active
,SUM(CASE WHEN bin.date_last_counted > 0 THEN 1 ELSE 0 END) AS total_counted
From bin
Group by bin.location_id
Order by bin.location_id) a

Combinations of Products as single count

I need to count combinations of products within transactions differently to other products and I'm struggling with how to do this within a single select statement from SQL 2008. This would then become a data set to manipulate in Reporting Services
raw data looks like this
txn, prod, units
1, a, 2
1, c, 1
2, a, 1
2, b, 1
2, c, 1
3, a, 2
3, b, 1
4, a, 3
4, c, 2
So a+b should = one if in same trans number, however a or b should equal one if not paired. So a=1 and b=1 but a+b=1, a+b+a=2, a+b+a+b=2 given the example data here is my desired result with an explanation of why
txn 1 is 3 units -- 2a + c
txn 2 is 2 units -- (a+b) + c
txn 3 is 2 units -- (a+b) + a
txn 4 is 5 units -- 3a + 2c
My query is more complex than this and includes other aggregates so I would like to group by transaction which I can't do as I need to manipulate at a lower grain
Update Progress :
Possible solution, I've generated columns based on the products I'm measuring. This allows me to group on Txn as I am now aggregating that field. Unsure if there's a better way to do it as it does take a little while
CASE WHEN SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end)=0
THEN SUM(CASE WHEN Prod='a' then 1 else 0 end)
ELSE 0 END AS MixProd
, CASE WHEN SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end)!=0
THEN ABS(SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end))
ELSE 0 END AS NotMixProd
I will then need to sort out the current unit aggregate to remove the extras but this certainly gives me a start
Update Progress 2 :
This failed to handle 0 correctly where a or b was 0 it would still give a value for mix because a-b was not zero. I reverted to an earlier draft that I lost and expanded as per below
, CASE WHEN SUM(CASE WHEN Prod='a' then 1 else 0 end) = 0 THEN 0
WHEN SUM(CASE WHEN Prod='b' then 1 else 0 end) = 0 THEN 0
WHEN SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end)=0
THEN SUM(CASE WHEN Prod='a' then 1 else 0 end)
ELSE ABS(SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end))
END AS MixProd
, CASE WHEN SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end)!=0
THEN ABS(SUM(CASE WHEN Prod='a' then 1 else 0 end)-
SUM(CASE WHEN Prod='b' then 1 else 0 end))
ELSE 0 END AS NotMixProd
UPDATE: This should work in SQL Server 2008 (based on LAG solution from here).
Here is the demo: http://rextester.com/GNI23706
WITH CTE AS
(
select txn, prod, units,
row_number() over (partition by txn order by prod) rn,
(row_number() over (partition by txn order by prod))/2 rndiv2,
(row_number() over (partition by txn order by prod)+1)/2 rnplus1div2,
count(*) over (partition by txn) partitioncount
from test_data
)
select
txn,
sum(case when prev_prod = 'a' and prod = 'b' and prev_units >= units then 0
when prev_prod = 'a' and prod = 'b' and prev_units < units then units - prev_units
else units
end) units
from
(
select
txn,
prod,
units,
CASE WHEN rn%2=1
THEN MAX(CASE WHEN rn%2=0 THEN prod END) OVER (PARTITION BY txn,rndiv2)
ELSE MAX(CASE WHEN rn%2=1 THEN prod END) OVER (PARTITION BY txn,rnplus1div2)
END AS prev_prod,
CASE WHEN rn%2=1
THEN MAX(CASE WHEN rn%2=0 THEN units END) OVER (PARTITION BY txn,rndiv2)
ELSE MAX(CASE WHEN rn%2=1 THEN units END) OVER (PARTITION BY txn,rnplus1div2)
END AS prev_units
from cte
) temp
group by txn
For SQL Server 2012+, use LAG:
select
txn,
sum(
case when prev_prod = 'a' and prod = 'b' and prev_units >= units then 0
when prev_prod = 'a' and prod = 'b' and prev_units < units then units - prev_units
else units
end) units
from
(
select
txn,
prod,
units,
lag(prod) over (partition by txn order by prod) prev_prod,
lag(units) over (partition by txn order by prod) prev_units
from test_data
) temp
group by txn
I decided in the end that a temp table was the best way to go, because I couldn't group on a collation. So I eventually tweaked the code above as it was failing to pick up the spare items correctly
SUM(Units) AS OldUnits
SUM(Units) -
(CASE WHEN
SUM(CASE WHEN Prod='a' THEN 1 ELSE 0 END) = 0 THEN 0 WHEN
SUM(CASE WHEN Prod='b' THEN 1 ELSE 0 END) = 0 THEN 0 WHEN
SUM(CASE WHEN Prod='a' THEN 1 ELSE 0 END) -
SUM(CASE WHEN Prod='b' THEN 1 ELSE 0 END) = 0 THEN
SUM(CASE WHEN Prod='a' THEN 1 ELSE 0 END) WHEN
(SUM(CASE WHEN Prod='a' THEN 1 ELSE 0 END) -
SUM(CASE WHEN Prod='b' THEN 1 ELSE 0 END)) < 0 THEN
SUM(CASE WHEN Prod='a' THEN 1 ELSE 0 END) ELSE
SUM(CASE WHEN Prod='b' THEN 1 ELSE 0 END) END) AS NewUnits
This was stored in a temptable that I could then collate on Trans as the next step. Works fine for my purposes and helped me overcome a mild irrational fear I have of temptables

How do you create a conditional count across multiple fields?

I have the huge table of over 100 million rows of data which is joined to another reference table that I want to create a conditional count for.
The first table is the large one which is an audit log and contains data which lists data on countries and contains a date of audit.
The second table is a smaller table which contains relational data to the audit log.
The first part is the easy bit which is to identify which audit data I want to see. I have the following code to identify this:
select aud.*
from audit_log aud
join database db on db.id=aud.release_id
where aud.event_description like '% opted in'
and r.creation_source = 'system_a'
This gives me the data in the following format:
Country Event Description Audit Date
Czech Republic Czech Republic has been automatically opted in 11-AUG-14 07.01.52.606000000
Denmark Denmark has been automatically opted in 12-AUG-15 07.01.53.239000000
Denmark Denmark has been automatically opted in 11-SEP-15 07.01.53.902000000
Dominican Republic Dominican Republic has been automatically opted in 11-SEP-15 07.01.54.187000000
Ecuador Ecuador has been automatically opted in 11-DEC-14 07.01.54.427000000
Ecuador Ecuador has been automatically opted in 11-NOV-14 07.01.54.679000000
The number of results from this query still returns over 5 million rows so I cannot export the data to Excel to create a count.
My two main issues are the number of rows and the date format of the 'Audit Date' field.
Ideally I want to create a count which shows the data as:
Country |Aug-14|Nov-14|Dec-14|Aug-15|Sep-15
Czech Republic | 1 | | | |
Denmark | | | | 1 | 1
Dominican Republic | | | | | 1
Ecuador | | 1 | 1 | |
Any idea's on how I extract the month and year and drop the figures into column by country?
Thanks
Edit - Thank you xQbert for you solution, it worked perfectly!
The problem now is that I have run into a new problem.
I need to constrain the count by another query, but there is no unique identifier between the tables involved.
For example, I amended your query to fit my db:
select cty.country_name,
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2014' then 1 else 0 end) as "AUG-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2014' then 1 else 0 end) as "SEP-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='OCT-2014' then 1 else 0 end) as "OCT-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='NOV-2014' then 1 else 0 end) as "NOV-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='DEC-2014' then 1 else 0 end) as "DEC-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JAN-2015' then 1 else 0 end) as "JAN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='FEB-2015' then 1 else 0 end) as "FEB-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAR-2015' then 1 else 0 end) as "MAR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='APR-2015' then 1 else 0 end) as "APR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAY-2015' then 1 else 0 end) as "MAY-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUN-2015' then 1 else 0 end) as "JUN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUL-2015' then 1 else 0 end) as "JUL-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2015' then 1 else 0 end) as "AUG-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2015' then 1 else 0 end) as "SEP-15"
from dschd.audit_trail aud
join dschd.release r on r.id=aud.release_id
join dschd.country cty on aud.EVENT_COUNTRY_ID=cty.id
where aud.event_description like '% opted in'
and r.creation_source = 'DSCHED'
GROUP BY cty.COUNTRY_name
My second query is:
select *
from DSCHD.RELEASE_COUNTRY_RIGHT rcr
join dschd.release r on rcr.RELEASE_ID=r.ID
join dschd.country cty on rcr.COUNTRY_ID=cty.id
where r.release_status in ('DRAFT', 'SCHEDULED', 'FINAL', 'DELIVERED')
and r.is_active = 'Y'
and rcr.MARKETING_RIGHT = 'Y'
and rcr.OPT_OUT = 'N'
and r.creation_source = 'DSCHED'
The problem is that I have many countries which can relate to one ID (Release_ID) but there is no unique identifier between the tables on a country level. Each country has an ID though.
So for query 1, to identify each unique row I would need the 'aud.Release_ID' and the 'aud.Event_country_id' and for query 2 to achieve the same I would need to use the 'rcr.Release_ID' and 'rcr.country_id'.
select cty.country_name,
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2014' then 1 else 0 end) as "AUG-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2014' then 1 else 0 end) as "SEP-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='OCT-2014' then 1 else 0 end) as "OCT-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='NOV-2014' then 1 else 0 end) as "NOV-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='DEC-2014' then 1 else 0 end) as "DEC-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JAN-2015' then 1 else 0 end) as "JAN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='FEB-2015' then 1 else 0 end) as "FEB-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAR-2015' then 1 else 0 end) as "MAR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='APR-2015' then 1 else 0 end) as "APR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAY-2015' then 1 else 0 end) as "MAY-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUN-2015' then 1 else 0 end) as "JUN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUL-2015' then 1 else 0 end) as "JUL-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2015' then 1 else 0 end) as "AUG-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2015' then 1 else 0 end) as "SEP-15"
from dschd.audit_trail aud
join dschd.release r on r.id=aud.release_id
join dschd.country cty on aud.EVENT_COUNTRY_ID=cty.id
where aud.event_description like '% opted in'
and ***** in (select ******
from DSCHD.RELEASE_COUNTRY_RIGHT rcr
join dschd.release r on rcr.RELEASE_ID=r.ID
join dschd.country cty on rcr.COUNTRY_ID=cty.id
where r.release_status in ('DRAFT', 'SCHEDULED', 'FINAL', 'DELIVERED')
and r.is_active = 'Y'
and rcr.MARKETING_RIGHT = 'Y'
and rcr.OPT_OUT = 'N'
and r.creation_source = 'DSCHED')
GROUP BY cty.COUNTRY_name
The bit I am stuck at are the two parts which are indicated by '*****' as the join criteria is two fields.
Any ideas?
Quick and dirty, not dynamic floating based on a 12 month cylce or anything...
select country,
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2014' then 1 else 0 end) as "AUG-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2014' then 1 else 0 end) as "SEP-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='OCT-2014' then 1 else 0 end) as "OCT-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='NOV-2014' then 1 else 0 end) as "NOV-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='DEC-2014' then 1 else 0 end) as "DEC-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JAN-2015' then 1 else 0 end) as "JAN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='FEB-2015' then 1 else 0 end) as "FEB-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAR-2015' then 1 else 0 end) as "MAR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='APR-2015' then 1 else 0 end) as "APR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAY-2015' then 1 else 0 end) as "MAY-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUN-2015' then 1 else 0 end) as "JUN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUL-2015' then 1 else 0 end) as "JUL-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2015' then 1 else 0 end) as "AUG-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2015' then 1 else 0 end) as "SEP-15"
from audit_log aud
join database db on db.id=aud.release_id
where aud.event_description like '% opted in'
and r.creation_source = 'system_a'
GROUP BY COUNTRY
Ideally we'd simply use a Pivot statement or base it on earliest date in range and go on... Such as found in this prior stack article Dynamic pivot in oracle sql
update based on changing requirements you do know you can join on multiple criteria right? :P
Note we created an inline view with your second query alias it as z table name and then add the two columns desired to match on as part of the results. Then we join it as if it were a table!
select cty.country_name,
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2014' then 1 else 0 end) as "AUG-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2014' then 1 else 0 end) as "SEP-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='OCT-2014' then 1 else 0 end) as "OCT-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='NOV-2014' then 1 else 0 end) as "NOV-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='DEC-2014' then 1 else 0 end) as "DEC-14",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JAN-2015' then 1 else 0 end) as "JAN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='FEB-2015' then 1 else 0 end) as "FEB-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAR-2015' then 1 else 0 end) as "MAR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='APR-2015' then 1 else 0 end) as "APR-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='MAY-2015' then 1 else 0 end) as "MAY-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUN-2015' then 1 else 0 end) as "JUN-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='JUL-2015' then 1 else 0 end) as "JUL-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='AUG-2015' then 1 else 0 end) as "AUG-15",
SUM(CASE WHEN to_char(Audit_Date,'MON-YYYY') ='SEP-2015' then 1 else 0 end) as "SEP-15"
from dschd.audit_trail aud
join dschd.release r on r.id=aud.release_id
join dschd.country cty on aud.EVENT_COUNTRY_ID=cty.id
join (select Release_ID, country_id
from DSCHD.RELEASE_COUNTRY_RIGHT rcr
join dschd.release r on rcr.RELEASE_ID=r.ID
join dschd.country cty on rcr.COUNTRY_ID=cty.id
where r.release_status in ('DRAFT', 'SCHEDULED', 'FINAL', 'DELIVERED')
and r.is_active = 'Y'
and rcr.MARKETING_RIGHT = 'Y'
and rcr.OPT_OUT = 'N'
and r.creation_source = 'DSCHED') Z
ON aud.Release_ID = z.Realease_ID and
aud.Event_country_id = z.country_id
where aud.event_description like '% opted in'
GROUP BY cty.COUNTRY_name

Proper way to create a pivot table with crosstab

How do I convert the following query into a pivot table using crosstab?
select (SUM(CASE WHEN added_customer=false
THEN 1
ELSE 0
END)) AS CUSTOMERS_NOT_ADDED, (SUM(CASE WHEN added_customer=true
THEN 1
ELSE 0
END)) AS CUSTOMERS_ADDED,
(select (SUM(CASE WHEN added_sales_order=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS SALES_ORDER_NOT_ADDED,
(select (SUM(CASE WHEN added_sales_order=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS SALES_ORDER_ADDED,
(select (SUM(CASE WHEN added_fulfillment=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS ITEM_FULFILLMENT_NOT_ADDED,
(select (SUM(CASE WHEN added_fulfillment=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS ITEM_FULFILLMENT_ADDED,
(select (SUM(CASE WHEN added_invoice=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS INVOICE_NOT_ADDED,
(select (SUM(CASE WHEN added_invoice=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS INVOICE_ADDED,
(select (SUM(CASE WHEN added_ra=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS RA_NOT_ADDED,
(select (SUM(CASE WHEN added_ra=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS RA_ADDED,
(select (SUM(CASE WHEN added_credit_memo=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS CREDIT_MEMO_NOT_ADDED,
(select (SUM(CASE WHEN added_credit_memo=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS CREDIT_MEMO_ADDED
FROM shipments_data;
This query gives me data in a standard row format however I would like to show this as a pivot table in the following format:
Added Not_Added
Customers 100 0
Sales Orders 50 50
Item Fulfillemnts 0 100
Invoices 0 100
...
I am using Heroku PostgreSQL, which is running v9.1.6
Also, I'm not sure if my above query can be optimized or if this is poor form. If it can be optimized/improved I would love to learn how.
The tablefunc module that supplies crosstab() is available for 9.1 (like for any other version this side of the millennium). Doesn't Heroku let you install additional modules? Have you tried:
CREATE EXTENSION tablefunc;
For examples how to use it, refer to the manual or this related question:
PostgreSQL Crosstab Query
OR try this search - there are a couple of good answers with examples on SO.
To get you started (like most of the way ..) use this largely simplified and re-organized query as base for the crosstab() call:
SELECT 'added'::text AS col
,SUM(CASE WHEN added_customer THEN 1 ELSE 0 END) AS customers
,SUM(CASE WHEN added_sales_order THEN 1 ELSE 0 END) AS sales_order
,SUM(CASE WHEN added_fulfillment THEN 1 ELSE 0 END) AS item_fulfillment
,SUM(CASE WHEN added_invoice THEN 1 ELSE 0 END) AS invoice
,SUM(CASE WHEN added_ra THEN 1 ELSE 0 END) AS ra
,SUM(CASE WHEN added_credit_memo THEN 1 ELSE 0 END) AS credit_memo
FROM shipments_data
UNION ALL
SELECT 'not_added' AS col
,SUM(CASE WHEN NOT added_customer THEN 1 ELSE 0 END) AS customers
,SUM(CASE WHEN NOT added_sales_order THEN 1 ELSE 0 END) AS sales_order
,SUM(CASE WHEN NOT added_fulfillment THEN 1 ELSE 0 END) AS item_fulfillment
,SUM(CASE WHEN NOT added_invoice THEN 1 ELSE 0 END) AS invoice
,SUM(CASE WHEN NOT added_ra THEN 1 ELSE 0 END) AS ra
,SUM(CASE WHEN NOT added_credit_memo THEN 1 ELSE 0 END) AS credit_memo
FROM shipments_data;
If your columns are defined NOT NULL, you can further simplify the CASE expressions.
If performance is crucial, you can get all aggregates in a single scan in a CTE and split values into two rows in the next step.
WITH x AS (
SELECT count(NULLIF(added_customer, FALSE)) AS customers
,sum(added_sales_order::int) AS sales_order
...
,count(NULLIF(added_customer, TRUE)) AS not_customers
,sum((NOT added_sales_order)::int) AS not_sales_order
...
FROM shipments_data
)
SELECT 'added'::text AS col, customers, sales_order, ... FROM x
UNION ALL
SELECT 'not_added', not_customers, not_sales_order, ... FROM x;
I also demonstrate two alternative ways to build your aggregates - both built on the assumption that all columns are boolean NOT NULL. Both alternatives are syntactically shorter, but not faster. In previous testes all three methods performed about the same.