Calculate difference in Oracle table from sum - sql

I have a table which looks as followed:
ID | Value
A | 2
A | 5
A | 6
B | 1
B | 7
B | -3
I am currently using a statement as followed
select ID, sum(VALUE)
where ...
group by ID.
Now I need the difference from A and B.
Could anyone send me on the right path? I am working with Oracle.

Use conditional aggregation:
SELECT SUM(CASE WHEN id = 'A' THEN "Value" ELSE 0 END) -
SUM(CASE WHEN id = 'B' THEN "Value" ELSE 0 END) "Difference"
FROM tablename;
See the demo.

Related

Using CASE to properly count items with if/else logic in SQL

Say I have a table table in the form:
| user | class |
|------|-------|
| 1 | a |
| 1 | b |
| 1 | b |
| 2 | b |
| 3 | a |
There are only two classes.
I want to write a query such that we count the number of users in each class such that any user who has label a and b gets sorted into a, any user with just a gets sorted into a and then any user with just b gets into b. If applied to the table snippet above we would get:
| class | count |
|-------|-------|
| a | 2 |
| b | 1 |
Also acceptable is the transpose, like:
| a | b |
|---|---|
| 2 | 1 |
My current solution involves two CTEs:
WITH a_users AS
(
SELECT
user,
SUM(CASE WHEN class = 'a' THEN 1 ELSE 0 END) AS a_class
FROM
table
WHERE
class in ('a', 'b')
GROUP BY
user
),
labeled_users as (
SELECT
user,
CASE WHEN a_class >=1 then 'a' ELSE 'b' END as label
FROM
a_users
)
SELECT
label,
COUNT(DISTINCT user)
FROM
labeled_users;
Is there a (1) more efficient way to solve for this or (2) a more concise/readable solution?
Basically, you want "a" for a user who has "a" at all. A subquery is the first approach:
select sum(case when num_as > 0 then 1 else 0 end) as num_class_a,
sum(case when num_as = 0 then 1 else 0 end) as num_class_b
from (select user, sum(case when class = 'a' then 1 else 0 end) as num_as
from t
group by user
) t;
With a little trick, you can eliminate the subquery:
select count(distinct case when class = 'a' then user end) as num_as,
count(distinct user) - count(distinct case when class = 'a' then user end) as num_bs
from t;
Something like this should work, if a and b really are your classes. Otherwise adjust the min/max as needed.
; with CTE as (
Select user, min(class) as Class
from Labeled_Users
group by user)
Select Class, count(*)
from CTE
group by Class
Here is a straight forward query to get the job done using a subquery and conditional aggregation. It should return the second version of your expected result (pivoted) :
SELECT
SUM(CASE WHEN x.minc <> x.maxc OR x.maxc = 'a' THEN 1 ELSE 0 END) a,
SUM(CASE WHEN x.minc = x.maxc AND x.maxc = 'b' THEN 1 ELSE 0 END) b
FROM (
SELECT user, MAX(class) maxclass, MIN(class) minclass
FROM mytable
GROUP BY user
) x
The subquery computes the minimum and maximum class of each user. Then the outer query separatly counts users :
a : users that belong to both classes or just to class a
b : users that belong to class b only
This is standard SQL syntax that will work on most RDBMS (obviously, even those who do not support CTEs, such as pre-8.0 MySQL versions).
Using String_agg():
with usr_class as(
SELECT DISTINCT usr,
string_agg(txt,':') as all_class
FROM abc
GROUP BY usr
)
select count(usr),
case when POSITION('a' in all_class)>0 THEN 'a'
ELSE 'b'
END AS CLASS
FROM usr_class
GROUP BY case when POSITION('a' in all_class)>0 THEN 'a'
ELSE 'b'
END;

How to find Sum of positive values and negative values separately in a column

If there is a column number which has 5 rows as follows:
+----------+
| values |
+----------+
| -2 |
| -1 |
| 0 |
| 1 |
| 2 |
| 3 |
+----------+
I need the sum of all negative values and the sum of all positive values in my resultset.
The problem context is not clear. So, I'll assume this is about data stored in a RDBMS table. I'll also assume you want to use SQL language to get results.
suppose table is DATA_TABLE and column name which interests us isVALUE then, query should look like
SELECT
SUM(CASE WHEN VALUE > 0 THEN VALUE ELSE 0 END) POSITIVE_BALANCE,
SUM(CASE WHEN VALUE < 0 THEN VALUE ELSE 0 END) NEGATIVE_BALANCE
FROM DATA_TABLE;
If assumptions are incorrect, please edit your question with more details, context of the questions and programming environment where you want to solve it.
Using a CASE expression:
SELECT SUM(CASE WHEN col > 0 THEN col ELSE 0 END) AS posSum,
SUM(CASE WHEN col < 0 THEN col ELSE 0 END) AS negSum
FROM yourTable
I guess you'll need to combine tow queries in one like this:
SELECT (SELECT SUM(col) FROM my_table WHERE col > 0) as positive_val,
(SELECT SUM(col) FROM my_table WHERE col < 0) as negative_val
You can try something real simple like this:
SLEECT * FROM
(SELECT SUM (VALUE) POS_SUM FROM TABLE WHERE VALUE > 0) A
JOIN
(SELECT SUM (VALUE) NEG_SUM FROM TABLE WHERE VALUE < 0) B
ON 1 = 1;
Vijaykymar’s solution would also work.
SELECT SUM(values) FROM yourTable WHERE value <> 0 GROUP BY SIGN(values)
Edit:
SQL> SELECT * FROM NUMBER_DATA WHERE value <> 0;
VALUE
----------
2
-3
4
5
-9
1
5
7
-8
10
SQL> SELECT SIGN(VALUE), SUM(VALUE) FROM NUMBER_DATA WHERE value <> 0 GROUP BY SIGN(VALUE);
SIGN(VALUE) SUM(VALUE)
----------- ----------
1 34
-1 -20

Multiple Groupings with a sum

My Table :
ID | TIME OF CREATION | OWNER | STATE
1 2015-1-1 arpan A
2 2015-1-2 arpan B
My desired o/p from my query is :
DATE | OWNER | COUNT(STATE = A) | COUNT(STATE = B) | ...
I checked out SUM( CASE ) but you cant group by date and sum by owner right?
Stuck here. :(
Can someone help?
I think you just want conditional aggregation:
select date, owner, sum(case when state = 'A' then 1 else 0 end) as state_A,
sum(case when state = 'B' then 1 else 0 end) as state_b
from table t
group by date, owner;

DB2 Select with group by and sum based on another column

I have the following situation:
ACCT_TABLE
ID|TYPE|AMT
--+----+---
A |CR | 5
A |DR | 5
B |CR | 2
B |CR | 4
B |DR | 2
B |DR | 2
C |CR | 1
C |CR | 1
I am trying to build a query that produces the following results:
DESIRED RESULT
ID|BAL
--+---
A | 0
B | 2
C | 2
ACTUAL RESULT
ID|BAL
--+----
A | -10
B | -8
C | 2
I'm not sure how to sum the values in amount based on the value in the TYPE column.
I have the following:
select id, sum(
case
when
type = 'CR'
then AMT
else -AMT
end
) as BAL
from acct_table
group by id;
I have tested your query in mysql, postgresql, sql-server,oracle and it produced desired results.
Your Query
SELECT id
,SUM(CASE WHEN type = 'CR' THEN AMT
ELSE -AMT
END) AS BAL
FROM acct_table
GROUP BY id;
My Query
SELECT id
,SUM(CASE WHEN type = 'CR' THEN AMT
ELSE ( AMT * -1 )
END) AS BAL
FROM acct_table
GROUP BY id;
oracle test - http://sqlfiddle.com/#!4/0b8cb/1
mysql test- http://sqlfiddle.com/#!2/0b8cbd
postgresql test - http://sqlfiddle.com/#!15/0b8cb/1
Please verify that data in your table matches what you have shown in the question.
I tested a query performing summing like that in DB2 (9.7 LUW), and it also worked.
There appears to be nothing wrong with your query; it must be something else.

SQL - Return A when B is exactly x,y,z

Consider the following table with column A and B:
A | B
--+--
1 | A
1 | B
1 | C
2 | A
2 | B
3 | A
3 | C
4 | B
4 | C
I would like to get the value 2 from column A in case my set is [A,B].
IN
select a from table where b IN ('A','B'), that will return value 1 and 2.
Intersect
select a from table where b = 'A'
intersect
select a from table where b = 'B'
intersect
select a from table where b = 'C', that will return 1 however it will not work if I for instance remove the 'B' criteria and only look for [A,C]. Such a query will return 1 and 3.
Is there a smarter way of using sets with one to many relations, or perhaps another approach I just did not think of? I will be using Oracle btw in case any Oracle specific solution should be available.
EDIT:
Use this for testing: SQLFiddle Link
This is an example of a "set-within-sets" query. I like to solve these using aggregation and a having clause for each condition. In this case, there are three conditions: Does a given value for A have a B with a value of 'A'? For 'B'? For anything else?
This results in the query:
select A
from t
group by A
having sum(case when B = 'A' then 1 else 0 end) > 0 and
sum(case when B = 'B' then 1 else 0 end) > 0 and
sum(case when B not in ('A', 'B') then 1 else 0 end) = 0;
Try this
select A
from t
group by A
having count(*)=2 and min(B)='A' and Max(B)='B'