1

I'm trying to sum / calculate values according to different "group by", but am unable to figure out how to do it in a single query.

CREATE TABLE test(
col1    text,
col2    text,
col3    int
);

INSERT INTO test VALUES('A',  '',  10);
INSERT INTO test VALUES('A', 'A',  15);
INSERT INTO test VALUES( '', 'A', 100);
INSERT INTO test VALUES('B', 'C',   1);
INSERT INTO test VALUES('C',  '',  33);

I've figured out how to partially get what I'm looking for:

--(this might not be the "correct" way, just my experiments)
SELECT col1 AS name, sum(col3) as col1_sum FROM test GROUP BY col1;
SELECT col2 AS name, sum(col3) as col2_sum FROM test GROUP BY col2;

In addition to the above, I would like a calculation of the difference b_sum - a_sum, so the complete query result would be:

name     col1_sum     col2_sum     difference
----     --------     --------     ----------
A          25             115          90
B           1         (empty)          -1
C          33               1         -32
(empty)   100              43         -57

Any ideas how to get the output described above..? Thanks!

6 Answers 6

2

I am thinking full outer join after aggregation:

select coalesce(t1.name, t2.name) as name, t1.col1_num, t2.col2_sum,
       coalesce(t2.col2_sum, 0) - coalesce(t1.col1_sum, 0) as diff
from (select col1 as name, sum(col3) as col1_sum
      from ttest
      group by col1
     ) t1 full outer join
     (select col2 as name, sum(col3) as col2_sum
      from ttest
      group by col2
     ) t2
     on t1.name = t2.name;
Sign up to request clarification or add additional context in comments.

Comments

1

Combine the results using UNION ALL and then subtract those values.

select name,max(col1_sum),max(col2_sum),coalesce(max(col2_sum),0)-coalesce(max(col1_sum),0)
from (
SELECT col1 AS name, sum(col3) as col1_sum,null col2_sum
FROM test 
GROUP BY col1
UNION ALL
SELECT col2, null, sum(col3) 
FROM test
GROUP BY col2
) t
GROUP BY name

Comments

1
with t as (
    select col1, col2, sum(col3), grouping((col1),(col2))
    from test
    group by grouping sets ((col1),(col2))
)
select
    coalesce(t1.col1, t1.col2) as col,
    t1.sum as col1_sum,
    t2.sum as col2_sum,
    coalesce(t2.sum, 0) - coalesce(t1.sum, 0) as difference
from t t1 full join t t2 on t1.col1 = t2.col2
where t1.col1 <> '' or t2.col2 <> '' or (t1.col1 = '' and t2.col2 = '')
;
 col | col1_sum | col2_sum | difference 
-----+----------+----------+------------
     |      100 |       43 |        -57
 A   |       25 |      115 |         90
 B   |        1 |          |         -1
 C   |       33 |        1 |        -32

Comments

0

using common table expressions:

;with a as (
SELECT 
    col1 AS name
  , sum(col3) as col1_sum 
FROM test 
GROUP BY col1
)
, b as (
SELECT 
    col2 AS name
  , sum(col3) as col2_sum 
FROM test 
GROUP BY col2
)
select 
    a.name
  , a.col1_sum
  , b.col2_sum
  , coalesce(b.col2_sum,0) - coalesce(a.col1_sum,0) as difference
from a
  left join b
    on a.name = b.name
order by a.name

rextester demo: http://rextester.com/YROWT76204

returns:

+------+----------+----------+------------+
| name | col1_sum | col2_sum | difference |
+------+----------+----------+------------+
|      |      100 | 43       |        -57 |
| A    |       25 | 115      |         90 |
| B    |        1 | NULL     |         -1 |
| C    |       33 | 1        |        -32 |
+------+----------+----------+------------+

If the first query doesn't have all the names, then switch from a left join to a full join and use coalesce(a.name,b.name) as name.

Comments

0
select a.name, col1_sum, col2_sum , coalesce(col2_sum,0) - coalesce(col1_sum,0) as diff
from
     (SELECT case when col1 is null then '(empty)' else col1 end AS name, 
      sum(col3) as col1_sum FROM test GROUP BY col1) a
full outer join (SELECT case when col2 is null then '(empty)' else col2 end AS name, 
      sum(col3) as col2_sum FROM test GROUP BY col2) b
on a.name = b.name
order by a.name;
name | col1_sum | col2_sum | diff
:--- | -------: | -------: | ---:
     |      100 |       43 |  -57
A    |       25 |      115 |   90
B    |        1 |     null |   -1
C    |       33 |        1 |  -32

dbfiddle here

Comments

0

First, I'd say that your solution is probably the simplest way to do it given the table structure.

Second, I suggest that you need a different table structure. "Name" should not be scattered around the table in different columns - it needs to be an indexed field. I'd try a table like this:

CREATE TABLE test(
aInstance   int,
aName    text,
col_nam    int,
col_val    int
);

INSERT INTO test VALUES(1,'A',1, 10);
INSERT INTO test VALUES(2,'A',1, 15);
INSERT INTO test VALUES(2,'A',2, 15);
INSERT INTO test VALUES(3,'A',2, 100);
INSERT INTO test VALUES(3,'B',1, 1);
INSERT INTO test VALUES(3,'C',2, 1);
INSERT INTO test VALUES(4,'C',2, 33);

This is less human-readable, but works better in a relational database.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.