(Oracle) How to get SUM values based on values in other columns? - sql

Suppose there is data like below:
ID Name Cost
ID1 A 10
ID1 A 60
ID1 B 20
ID1 C 20
ID2 B 10
ID2 B 50
ID2 C 50
ID3 B 5
Here in the table above, ID and NAME are not unique.
And I want to get SUM values based on NAME, so the expected result is like below:
ID A_Costs B_Costs C_Costs AB_Costs
ID1 70 20 20 90
ID2 60 50 60
ID3 5 5
A_Cost, B_Costs, and C_Costs are costs when the name is A, B or C.
But what do I do if I want to get costs when the name is A and B?
So what I was trying to do was this:
Select t2.ID,
SUM(DECODE (t2.name, 'A', t2.Cost, null)),
SUM(DECODE (t2.name, 'B', t2.Cost, null))
--(select sum(t1.cost) from table t1. where t1.name in ('A','B') and t1.id = t2.id)
from table t2
group by t2.id
But this does not work.
How do I get the costs when the name is A and B like the line I commented out? Is there any effective way to get the value like that in one query?
Thank you in advance.

If you want to use decode(), you can do:
sum(decode(t2.name, 'A', t2.cost, 'B' t2.cost))
Or you can use a case statement:
sum(case when t2.name in ('A', 'B') then t2.cost end)
Full query:
select id,
sum(case when name = 'A' then cost end) as a_costs,
sum(case when name = 'B' then cost end) as b_costs,
sum(case when name = 'C' then cost end) as c_costs,
sum(case when name IN ('A', 'B') then cost end) as ab_costs
from SomeTable
group by id
order by id
SQL Fiddle Demo

You will also have to aggregate after using sum in the inner query.
select
id, max(a_cost) as A_Costs, max(b_cost) as B_Costs,
max(c_cost) as C_Costs, nvl(max(a_cost),0) + nvl(max(b_cost),0) as AB_Costs
from (
select ID,
sum(case when name = 'A' then cost end) as a_cost,
sum(case when name = 'B' then cost end) as b_cost,
sum(case when name = 'C' then cost end) as c_cost
from table
group by id
) t
group by id

Related

Subtract from rows with certain values

I have a table created in Materialized view from 10 different tables.
Part of it looks like this
group_name
value1
value2
group1
100
20
group2
200
40
unknown
300
60
TOTAL
600
120
I have to rearrange all values from rows with value group_name = 'unknown' to other rows. The final table should look like this
group_name
value1
value2
group1
200
40
group2
400
80
TOTAL
600
120
So formula for 'group1' would be:
unknown x group1 x (TOTAL-unknown) + group1
The table is created with massive code and please note - I didn't write it, it was given to me and I have to work with it. I don't like how it looks, so please spare your anger. Anyway, the query looks like this:
TABLESPACE pg_default
AS
WITH table_value1 AS (
SELECT
table1.group_name,
table1.value1,
FROM table1
), table_value2 AS (
SELECT
table2.group_name,
table2.value2,
FROM table2
), TOTAL_groups AS (
SELECT
'value1'::text AS group_name,
sum(xy_table."value1")::numeric as results
FROM xy_table
UNION ALL
SELECT
'value2'::text AS group_name,
sum(xy_table."value2")::numeric as results
FROM xy_table
UNION ALL
SELECT
'unknown'::text AS group_name,
sum(xy_table."unknown")::numeric as results
FROM xy_table
), TOTAL AS (
SELECT
TOTAL_groups.group_name,
TOTAL_groups.results
FROM TOTAL_groups
UNION ALL
'TOTAL'::text AS group_name,
round(sum(TOTAL_groups.raba), 1) as results
FROM skupaj_energenti
)
SELECT
a.group_name,
COALESCE(a.results, 0::numeric) AS value1,
COALESCE(a.results, 0::numeric) AS value2
FROM table_value1 a
LEFT JOIN table_value2 b ON b.group_name = a.group_name
LEFT JOIN TOTAL c ON f.group_name = a.group_name
WITH DATA;
I have no idea how should I write such conditions in SQL. Please help.
Distribute 'unknown' row to other rows. Assuming value1, value2 are DECIMAL
select group_name, value1 * (1 + k1) value1, value2 * (1 + k2) value2
from tbl
cross join (
select sum(case group_name when 'unknown' then value1 end) / sum(case group_name when 'TOTAL' then value1 else -value1 end) k1,
sum(case group_name when 'unknown' then value2 end) / sum(case group_name when 'TOTAL' then value2 else -value2 end) k2
from tbl
where group_name in ('TOTAL', 'unknown')
) t
where tbl.group_name not in ('TOTAL', 'unknown')
db<>fiddle

Keep multiple rows during PIVOT in Snowflake

I would like to transpose rows into columns in Snowflake.
Suppose I have the following table BASE
ID
value
type
1
100
'A'
1
200
'B'
1
300
'B'
2
400
'A'
The output should be as follows:
ID
A
B
1
100
200
1
100
300
2
400
NULL
Currently I am pivoting the table with
SELECT ID,
CASE WHEN TYPE = 'A' THEN VALUE ELSE NULL AS A,
CASE WHEN TYPE = 'B' THEN VALUE ELSE NULL AS B
FROM BASE
For now the GROUP BY statement is missing. Typically I would GROUP BY ID, but that does not account for keeping one row per each value on the same TYPE and ID.
Any ideas how to achieve this?
Cheers,
P
You can use conditional aggregation. You can use row_number() to get multiple rows:
SELECT ID,
MAX(CASE WHEN TYPE = 'A' THEN VALUE END) AS A,
MAX(CASE WHEN TYPE = 'B' THEN VALUE END) AS B
FROM (SELECT B.*,
ROW_NUMBER() OVER (PARTITION BY ID, TYPE ORDER BY VALUE) as seqnum
FROM BASE B
) B
GROUP BY ID, seqnum;
This would work, too:
select *
from base_table
pivot(sum(value) for type in ('A','B')) as p
order by id;

Comparing a particular column value for two rows in SQL

I have a table as follows:
ID Version Type Value
121 1 A 100
121 1 B 200
122 2 A 300
122 2 B 300
123 3 A 300
123 3 B 300
124 4 A 420
124 4 B 420
121 3 A 410
121 3 B 410
I am trying to build a query which gives returns the rows where "Value" is different between Type A and Type B for same ID and Version.
For example, in the above scenario, ID and Version is Unique 5 times (121-1,122-2,123-3,124-4,121-3). I would look at "Type" for each unique group and compare "Value" for Type A and Type B.
The above scenario should return:
ID Version Type Value
121 1 A 100
121 1 B 200
The above two rows are the only ones with different "Value" for unique ID and Version and among Type A and Type B.
Any help will be greatly appreciated :)
You can use exixts :
select t.*
from table t
where exists (select 1
from table t1
where t1.id = t.id and
t1.Version = t.Version and
t1.type <> t.type and
t1.Value <> t.Value
);
You can join a table to itself:
SELECT *
FROM [MyTable] t0
INNER JOIN [MyTable] t1 ON t1.ID = t0.ID AND t0.Type <> t1.Type AND t0.Value <> t1.Value
or
SELECT *
FROM [MyTable] t0
INNER JOIN [MyTable] t1 ON t1.ID = t0.ID AND t1.Type = 'B' AND t0.Value <> t1.Value
WHERE t0.Type = 'A'
If you have only two types, I am going to recommend a different result set format:
select id, version,
max(case when type = 'A' then value end) as value_a,
max(case when type = 'B' then value end) as value_b
from t
where type in ('A', 'B')
group by id, version,
having max(case when type = 'A' then value end) <> max(case when type = 'B' then value end);
I recommend this because a typical follow-on question might be "how big is the difference".
This is one way to approach this problem:
select *
from t1
where (id, value, version) in (select id, value, version
from t1
group by id, value, version
having count(id) = 1);
Here is a DEMO
This is what I ended up doing:
SELECT MAX(Value), MIN(Value)
FROM table_name
GROUP BY ID, Version
HAVING MAX(Value)<>MIN(Value);

Union three tables and show where data came from

have no idea how to solve following:
There are three tables each with a column of names, for example
Table 1 - column name 'Name' - values 'A', 'B' and 'C'
Table 2 - column name 'Name' - values 'A' and 'B'
Table 3 - column nane 'Name' - values 'A' and 'C'
The goal is to UNION the tables - each value of the three tables should be shown only one time. In addition there should be three new "virtual columns" showing in which table the value is included('1' when value is included, '0' if not). So the result should look like this:
Value | Table1 | Table2 | Table3
--------------------------------
A | 1 | 1 | 1
B | 1 | 1 | 0
C | 1 | 0 | 1
Hope someone can help me, thanks in advance.
Does this do what you want?
select Name, max(Table1) as Table1, max(Table2) as Table2, max(Table3) as Table3
from (select Name, 1 as Table1, 0 as Table2, 0 as Table3
from table1
union all
select Name, 0 as Table1, 1 as Table2, 0 as Table3
from table2
union all
select Name, 0 as Table1, 0 as Table2, 1 as Table3
from table3
) t
group by Name;
You might want to use sum() instead of max() to get the number of times the value occurs in each table.
If your db supports full joins you can try the query below.
select
coalesce(t1.Name,t2.Name,t3.Name) myValue,
(case when max(t1.Name) is not null then 1 else 0 end) c1,
(case when max(t2.Name) is not null then 1 else 0 end) c2,
(case when max(t3.Name) is not null then 1 else 0 end) c3
from
Table1 t1
full join Table2 t2 on t1.Name = t2.Name
full join Table3 t3 on t1.Name = t3.Name
group by coalesce(t1.Name,t2.Name,t3.Name)
If you know that a value will not appear more than once in each table, you can remove the group by and max parts.
Here's my attempt (update: works f.e. in SQL-Server 2005 and upwards due to the CTE):
With names AS
(
SELECT Name, Source = 'T1'
FROM dbo.Table1
UNION ALL
SELECT Name, Source = 'T2'
FROM dbo.Table2
UNION ALL
SELECT Name, Source = 'T3'
FROM dbo.Table3
)
SELECT n.Name,
Table1 = CASE WHEN EXISTS
(SELECT 1 FROM names n2
WHERE Source = 'T1' AND n2.Name=n.Name)
THEN 1 ELSE 0 END,
Table2 = CASE WHEN EXISTS
(SELECT 1 FROM names n2
WHERE Source = 'T2' AND n2.Name=n.Name)
THEN 1 ELSE 0 END,
Table3 = CASE WHEN EXISTS
(SELECT 1 FROM names n2
WHERE Source = 'T3' AND n2.Name=n.Name)
THEN 1 ELSE 0 END
FROM names n
GROUP BY n.Name
Demo

To retrieve records having only two specific values

Have the following Data in the table
Example Table
ID Value
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
I need to retrieve records having ID with only two values a and b.
So i am expecting only the Record with ID 3 .
Can anyone help me with the query
I guess you could do something like
select
ID,
sum(case when value = 'a' then 1
when value = 'b' then 1
else 3 end)
from
table1
group by id
having
sum (case when value = 'a' then 1
when value = 'b' then 1
else 3 end) =2
SQL Fiddle
That will work:
select x.id from
(
select id from mytable where value = 'a'
union all
select id from mytable where value = 'b'
) x
group by x.id
having COUNT(*) = 2
and not exists (select * from mytable t where t.id = x.id and value <> 'a' and value <> 'b')