Oracle SQL Conditional SUM - sql

I have a table of names and numeric values and I want to sum values grouped by names. This part is simple:
SELECT
name,
SUM(my_value) "MyValue"
FROM my_table
GROUP BY name
But I also have string 'UNLIMITED' in values. When there is 'UNLIMITED' in the group, I just want to select value 'UNLIMITED' and not do any sum. This is what I came up with using UNION but I know there is a better way:
SELECT
name,
MAX("MyValue")
FROM (
SELECT
name,
'UNLIMITED' "MyValue"
FROM my_table
WHERE my_value = 'UNLIMITED'
GROUP BY name
UNION
SELECT
name,
TO_CHAR(SUM(
CASE WHEN my_value = 'UNLIMITED'
THEN '0'
ELSE my_value END
)) "MyValue"
FROM my_table
GROUP BY name
) t
GROUP BY name
Please check SqlFiddle for real example.
Example table
NAME MY_VALUE
name1 50
name1 20
name2 30
name2 UNLIMITED
Example wanted result
NAME SUM("MYVALUE")
name1 70
name2 UNLIMITED

This is a pretty easy way to express the logic:
SELECT name,
(CASE WHEN MAX(my_value) = 'UNLIMITED' THEN 'UNLIMITED'
ELSE TO_CHAR(SUM(CASE WHEN my_value <> 'UNLIMITED' THEN my_value END))
END)
FROM my_table
GROUP BY name;
This uses the fact that characters are ordered after numbers.
Or similar logic:
SELECT name,
(CASE WHEN COUNT(*) <> COUNT(NULLIF(my_value, 'UNLIMITED')) THEN 'UNLIMITED'
ELSE TO_CHAR(SUM(NULLIF(my_value, 'UNLIMITED')))
END)
FROM my_table
GROUP BY name;

One way to do it with window functions.
SELECT DISTINCT
name,
CASE WHEN sum(case when my_value = 'UNLIMITED' then 1 else 0 end) over(partition by name) >= 1
THEN 'UNLIMITED'
ELSE cast(sum(case when my_value = 'UNLIMITED' then 0 else cast(my_value as number) end) over(partition by name)
as varchar(255))
end as myval
FROM my_table

This works
create table tempxx (a nvarchar2(10), b nvarchar2(20))
insert into tempxx values ('a', 50);
insert into tempxx values ('a', 20);
insert into tempxx values ('b', 30);
insert into tempxx values ('b', 'UNLIMITED');
SELECT allt.a, decode(ut.max, NULL, to_char(allt.sum), to_char(ut.max)) as val
From
((SELECT
a,
sum(decode(b, 'UNLIMITED', 0, b)) sum
FROM tempxx
Group by a) allT left join
(SELECT
a,
Max(b) max
FROM tempxx
WHERE b = 'UNLIMITED'
Group by a) ut on allt.a = ut.a)
A VAL
------------
b UNLIMITED
a 70
Basically, select all rows on left and join with only unlimited. If unlimited record is null, keep left one, otherwise take unlimited data

Related

How to check if any value in group is equal to a specific value in BigQuery SQL?

I have a dataset like the following:
ID|Date_Val|Data
1|2022-01-01|A
1|2022-01-01|I
1|2022-01-01|H
2|2022-01-01|G
2|2022-01-01|G
2|2022-01-01|I
I would like to run a query like the following:
SELECT ID, Date_Val, IF(/logic here/, 'A', 'B')
GROUP BY 1,2
Output dataset
ID|Date_Val|Data
1|2022-01-01|A
2|2022-01-01|B
How would I write /logic here/ so that if any Data value in the grouping (ID, Date_Val) is = 'A' then 'A' else 'B'.
We can try:
SELECT ID, Date_Val,
CASE WHEN MAX(CASE WHEN Data = 'A' THEN 1 END) > 0 THEN 'A' ELSE 'B' END AS Data
FROM yourTable
GROUP BY 1, 2;
SELECT ID, Date_Val,
CASE WHEN SUM(CASE WHEN Data = 'A' THEN 1 ELSE 0 END) >= 1 THEN 'A' ELSE 'B' END as Data
FROM your_table
GROUP BY ID, Date_Val
In BigQuery, another option would be:
SELECT ID, Date_Val, IF('A' IN UNNEST(ARRAY_AGG(Data)), 'A', 'B') Data
FROM sample_table
GROUP BY 1, 2;
Below is most compact version so far for you to consider
select id, date_val,
if(logical_or(data = 'A'), 'A', 'B') as data
from your_table
group by id, date_val
if applied to sample data in your question - output is

How to flatten a SQL statement

I have a case statement
Select customer, group, case when group = one then 'A' else 'B' end as Indicator FROM TABLE1
How do I "flatten" the indicator so for each customer I have 2 column for each indicator type (Goal Table)
Current Table:
Customer
Group
Indicator
Joh
One
A
Joh
Two
B
Jane
One
A
Jane
Two
B
Goal Table:
Customer
Indicator1
Indicator2
Joh
A
B
Jane
A
B
Since values are being hard-coded ('A','B') for indicator column, we can use max, as it will yield one value only -
with data_cte(Customer,Group_1,Indicator) as(
select * from values
('Joh','One','A'),
('Joh','Two','B'),
('Jane','One','A'),
('Jane','Two','B')
)select d.customer
,max(case when d.group_1 = 'One' then 'A' end) as indicator1
,max(case when d.group_1 = 'Two' then 'B' end) as indicator2
from data_cte d
group by d.customer;
The form of Pankaj's answer is good if you have fixed group's, but his code has the indicator values hard coded, this it should look like:
with data_cte(Customer, Group_1, Indicator) as (
select *
from values
('Joh','One','A'),
('Joh','Two','B'),
('Jane','One','A'),
('Jane','Two','B')
)
select
d.customer
,max(case when d.group_1 = 'One' then d.indicator end) as indicator1
,max(case when d.group_1 = 'Two' then d.indicator end) as indicator2
from data_cte as d
group by 1;
The CASE in the MAX can be swapped for a IFF in the form
MAX(IFF(d.group_1 = 'One` then d.indicator, null)) as indicator1
This works as MAX takes the larest value, so if you only have one matching group_1 per customer, the other will be null and those are not larger so the wanted value is taken.
If you have many, you will want to somehow rank then, and then FIRST_VALUE with a partition on customer, and ordered by something like a date..
anyways, if you have unkown/dynamic columns this can be solve using Snowflake Scripting to double query the data.
create or replace table table1 as
select column1 customer, column2 as _group, column3 as indicator
from values
('Joh',1,'A'),
('Joh',2,'B'),
('Jane',1,'C'),
('Jane',3,'E'),
('Jane',2,'D');
declare
sql string;
res resultset;
c1 cursor for select distinct _group as key from table1 order by key;
begin
sql := 'select customer ';
for record in c1 do
sql := sql || ',max(iff(_group = '|| record.key ||', indicator, null)) as col_' || record.key::text;
end for;
sql := sql || ' from table1 group by 1 order by 1';
res := (execute immediate :sql);
return table (res);
end;
gives:
CUSTOMER
COL_1
COL_2
COL_3
Jane
C
D
E
Joh
A
B
null

SQL Select a specific value in the group

I have this following table
Dept---------- Sub_Dept---- Dept Type
Sales.............Advertising........A
Sales.............Marketing......... B
Sales.............Analytics.......... C
Operations.....IT..................... C
Operations.....Settlement........C
And the result should be if a department got a department type as A then change all record of that department to A, else keep it same
Dept---------- Sub_Dept---- Dept Type
Sales.............Advertising........A
Sales.............Marketing......... A
Sales.............Analytics.......... A
Operations.....IT..................... C
Operations.....Settlement........C
Anybody can give a suggestion on this? I thought of using the GROUP BY but have to output the Sub Department as well
Thanks a lot
I would do:
update t
set depttype = 'a'
where exists (select 1 from t t2 where t2.dept = t.dept and t2.dept = 'a') and
t.dept <> 'a';
If you just want a select, then do:
select t.*,
(case when sum(case when depttype = 'a' then 1 else 0 end) over (partition by dept) > 1
then 'a'
else depttype
end) as new_depttype
from t;
Use below query
select a11.dept, a12.Sub_Dept, (case when a12.min_dep_type='A' then 'A' else a11.dep_type) as dep_type
from tab a11
JOIN (select dept, min(dep_type) min_dep_type from tab group by dept) a12
on a11.dept = a12.dept
Try this:
update table
set depttype= case when dept in (select dept from table where depttype='a') then 'a' else depttype end
This should work:
select a.dept, a.sub_dept,
case when b.dept is not null then 'A' else dept_type end as dept_type
from aTable a
left join(
select distinct Dept from aTable where dept_type = 'A'
)
b on b.dept = a.dept
You could use analytic functions to check whether exists the specific value in the group.
Try below query:
SELECT t.Dept,
t.Sub_Dept,
NVL(MIN(CASE WHEN t.Dept_Type = 'A'
THEN Dept_Type END) OVER (PARTITION BY t.Dept), t.Dept_Type) AS Dept_Type
FROM table_1 t
Using the analytic function MIN(), you can search for the value of 'A' (if it does exist inside the group). MIN works for non-null values only, so if you don't have any 'A' in the group, the result will be NULL.
At this point, you can use NVL to choose whether to print the value found in the group or the actual dept_type of the row.

How do I find all values where all rows associated with that value meet a condition?

I have a table in SQL that is structured as follows:
Name Value1 Value2
A .2 .3
A .1 .05
A .5 .3
B .2 .4
B .1 .08
C .3 .4
C .2 .5
C .1 .3
How do I get all the Names where Value1 is less than Value2 for every row associated with a Name? In the example above, I would only want to pull out Name C.
I read this article: Select in MySQL where all rows meet a condition
but I don't think this applies to my problem, as this solution assumes you are comparing a row to fixed value.
A variant on some of the other answers, also using conditional aggregation:
SELECT Name
FROM yourTable
GROUP BY Name
HAVING SUM(CASE WHEN value1 >= value2 THEN 1 ELSE 0 END) = 0;
The above query asserts that each matching name group does not have any record for which value1 is greater than or equal to value2.
You can use group by and having:
select name
from t
group by name
having count(*) = sum(case when value1 < value2 then 1 else 0 end);
There are other ways to phrase this, such as:
select distinct name
from t
where not exists (select 1 from t t2 where t2.name = t.name and t2.value2 >= t2.value1);
Or:
select name
from t
except
select name
from t
where value2 >= value1;
You can get desired result with in clause
declare #table table (name varchar(10), value1 float, value2 float)
insert #table
select 'A', '0.2', '.3'
union all
select 'A' , '.1', '.05'
union all
select 'A', '.5', '.3'
union all
select 'B', '.2', '.4'
union all
select 'B', '.1' , '.08'
union all
select 'C', '.3' , '.4'
union all
select 'C', '.2' , '.5'
union all
select 'C' , '.1' , '.3'
Use In Clause where value1<value2
select * from #table where value1<value2
and name not in ( select name from #table where value1>value2)
You can use EXIST to check if the value exist in your desired condition wherein all rows by certain name have value2 greater than value1.
SELECT *
FROM TableName a
WHERE EXISTS (SELECT 1
FROM TableName b
WHERE a.name = b.Name
GROUP BY b.name
HAVING COUNT(case when b.value1 < b.value2 THEN 1 END) = COUNT(*))
Here's a Demo.
This will display all rows for the valid name. However, check other answer if you only want to display distinct name.

Group 2 rows into 1 in Oracle using SQL

I have a table which has data like
state total
A 3
B 6
C 2
D 7
E 4
I need to generate a table from this that has total of A & B (true) together and C, D, E (False) together
Result Table
Status Total
True 9 (sum of A and B )
False 13 (sum of C, D, E)
Any ideas how to do this using SQL? I am doing this in Oracle
SELECT nstate, SUM(total)
FROM (
SELECT DECODE(state, 'A', 'True', 'B', 'True', 'False') AS nstate, total
FROM mytable
)
GROUP BY
nstate
I'd use a UNION query
SELECT 'True' AS Status, SUM(total) AS Total
FROM table
WHERE state IN ('A', 'B')
UNION
SELECT 'False' AS STATUS, SUM(total) AS Total
FROM table
WHERE state IN ('C', 'D', 'E')
ORDER BY Status DESC;
You may need to group by Status on each query but I'm not sure as the column is virtual / static / scalar
I like CASE - I think it's easier to interpret than DECODE:
CREATE TABLE RESULT_TABLE AS
SELECT STATE, SUM(TOTAL) AS TOTAL
FROM (SELECT CASE STATE
WHEN 'A' THEN 'True'
WHEN 'B' THEN 'True'
ELSE 'False'
END AS STATE,
TOTAL
FROM MY_TABLE)
GROUP BY STATE;
Share and enjoy.