Group 2 rows into 1 in Oracle using SQL - 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.

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

Oracle SQL Conditional SUM

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

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.

Query to fetch data matching multiple values across DB rows

Hi I need help on a sql query. The result must match values for a single column across the rows. Here is an example. I need to find out store(s) that must have all of these items for sale: Books, Stationery, and Toys.
Store Items
----- --------
AA PERFUMES
AA TOYS
BB STATIONERY
BB BOOKS
BB TOYS
In the example above, "BB" is the only store that matches all of our criteria and hence the result expected from the query.
I tried query with AND operator (select store from storeitem where items = 'books' and items ='toys' and items='stationery';) and it did not work as it expects all values in the same row and with in operator (select store from storeitem where items in ('books','stationery','toys');) , this doesn't follow must match all values criteria.
Need your help on this.
You could skip using subqueries alltogether and use a HAVING DISTINCT clause to return the stores you need.
SELECT store, COUNT(*)
FROM your_table
WHERE items in ('STATIONAIRY', 'BOOKS', 'TOYS')
GROUP BY
store
HAVING COUNT(DISTINCT items) = 3
;
Example
WITH your_table as (
SELECT 'AA' as Store, 'PERFUMES' as Items FROM dual UNION ALL
SELECT 'AA', 'TOYS' FROM dual UNION ALL
SELECT 'BB', 'STATIONAIRY' FROM dual UNION ALL
SELECT 'BB', 'BOOKS' FROM dual UNION ALL
SELECT 'BB', 'TOYS' FROM dual
)
SELECT store, COUNT(*)
FROM your_table
WHERE items in ('STATIONAIRY', 'BOOKS', 'TOYS')
GROUP BY
store
HAVING COUNT(DISTINCT items) = 3
;
select store
from (
select distinct store, items
from your_table
where items in ('books','stationery','toys')
)
group by store
having count(0) = 3
This is the general approach that should work (not tested on Oracle specifically):
select store from (
select store,
max(case when items = 'stationery' then 1 else 0 end) as has_stationery,
max(case when items = 'books' then 1 else 0 end) as has_books,
max(case when items = 'toys' then 1 else 0 end) as has_toys
from your_table
group by store
) as stores_by_item
where has_stationery = 1 and has_books = 1 and has_toys = 1
If I correctly understand your question, you needed that query:
Select store from storeitem where store in (select store from storeitem where items = 'books') AND store in (select store from storeitem where items ='toys') AND store in (select store from storeitem where items='stationairy')