You can use a conditional number:
with trans_detail as ( select 1 as trans_id, 100 as trans_amount from dual union all select 1 as trans_id, 200 as trans_amount from dual union all select 2 as trans_id, -100 as trans_amount from dual union all select 2 as trans_id, -300 as trans_amount from dual union all select 3 as trans_id, 400 as trans_amount from dual union all select 3 as trans_id, -500 as trans_amount from dual ) select trans_id, count(case when trans_amount >= 0 then trans_id end) as pos_count, count(case when trans_amount < 0 then trans_id end) as neg_count from trans_detail group by trans_id order by trans_id; TRANS_ID POS_COUNT NEG_COUNT
Count ignores null values, so the implicit null 'else' for each case means that these lines are not counted. You can add else null if you want, but that just makes it a little longer. (I included zero as "positive", but you can completely ignore it, as in my question, in which case just go back to > 0 ).
SQL Fiddle
You can also use the sign function either in the case or in the decode:
select trans_id, count(decode(sign(trans_amount), 1, trans_id)) as pos_count, count(decode(sign(trans_amount), -1, trans_id)) as neg_count from trans_detail group by trans_id order by trans_id;
SQL Fiddle it ignores zero, but you can include it in decoding if you want.
source share