SELECTING aggregate data using CASE statement issue - sql

I'm trying to create a query to obtain zone data in preparation to moving the column to another table. How our DB is set up is that we have assets and cables. One asset can have many cables but a cable cannot have multiple assets. We're currently trying to move the Zone field from the Cable table (ex. Below) to the Asset table.
A_ID C_ID Zone
--------------------------
1 1 Green
1 2 Green
1 3 Yellow
2 4 Green
2 5 Red
3 6 Yellow
3 7 Yellow
3 8 Yellow
3 9 Red
The way I want it to be set up for the Asset table is if the Asset contains multiple cables with different zones, if one of the zones is Yellow it defaults to Yellow (ex. 3 green cables, 1 yellow cable - Asset_ID has Yellow zone). Next if it doesn't have any Yellows but has at least 1 red, it defaults to Red (ex. 2 green cables, 3 red cables - Asset_ID has Red Zone). Only if it just has Green zones then it defaults to Green.
Using the sample table above these are the results I would expect.
Expected Results
A_ID Zone
-------------
1 Yellow
2 Red
3 Yellow
I'm trying to use CASE statements but I'm having difficulty formulating the query to group the results correctly.
Any help would be greatly appreciated, thank you in advance.

One way to do it using conditional aggregation and a case expression.
select a_id
,case when yellow_count >=1 then 'Yellow'
when yellow_count = 0 and red_count >=1 then 'Red'
when yellow_count = 0 and red_count = 0 and green_count >=1 then 'Green'
end zone
from (select a_id,
count(case when zone = 'Yellow' then 1 end) yellow_count,
count(case when zone = 'Red' then 1 end) red_count
count(case when zone = 'Green' then 1 end) green_count
from cable_table
group by a_id) t

No case or if statement needed. Think in sets. You need to assign a priority join to the priority and select the one with the highest priority first. Like this
WITH Priority AS
(
SELECT * FROM (
VALUES
('Yellow', 1),
('Red', 2),
('Green', 3)
) AS P(Zone,P)
)
SELECT A_ID, Zone
FROM (
SELECT A_ID, Zone
ROW_NUMBER() OVER (PARTITION BY A_ID ORDER BY P.P ASC) AS RN
FROM AssetTable A
LEFT JOIN Priority P ON A.Zone = P.Zone
) SUB
WHERE RN = 1
Not sure if my syntax is right for VALUES in CTE for Oracle. if that gives an error replace with this:
WITH Priority AS
(
SELECT 'Yellow' AS Zone, 1 AS P
UNION ALL
SELECT 'Red' AS Zone, 2 AS P
UNION ALL
SELECT 'Green' AS Zone, 3 AS P
)

Why just don't do a simple:
SELECT A_ID, MAX( Zone )
FROM table
GROUP BY A_ID
if some A_ID has Yellow then max( Zone ) returns Yellow
if some A_ID hasn't Yellow, but has Red then max( Zone ) returns Red<br>
otherwise (noYellownorRed) max( Zone ) returnsGreen`
and example:
with data as (
select 1 a_id, 1 c_id , 'Green' zone from dual union all
select 1 , 2 , 'Green' from dual union all
select 1 , 3, 'Yellow' from dual union all
select 2 , 4 , 'Green' from dual union all
select 2 , 5 , 'Red' from dual union all
select 3 , 6 , 'Yellow' from dual union all
select 3 , 7 , 'Yellow' from dual union all
select 3 , 8 , 'Yellow' from dual union all
select 3 , 9 , 'Red' from dual
)
select a_id, max( zone )
from data
group by a_id
A_ID MAX(ZO
---------- ------
1 Yellow
2 Red
3 Yellow

Related

what is wrong with my query in oracle live sql?

I ran the following code in oracle live SQL but it gives an error. What is the problem?
select * from bricks full join
((select count(*) from bricks) as "counts"
inner join (select count(*) from bricks group by colour) as "colours"
on counts.colour=colours.colour)
on bricks.colour=bricks.colour; --a dummy condition just for connecting tables without a specific column
output:
ORA-00907: missing right parenthesis
The problems are:
AS before a table/sub-query alias is invalid syntax in Oracle. Remove the AS keywords.
The aliases you are using are quoted identifiers which are case-sensitive and you do not use quoted identifiers with the same case when you refer to the aliases. Remove the double quotes.
Neither the counts nor colours sub-query has a colour column in the SELECT clause and you cannot subsequently refer to non-existent columns in the ON clause of the join condition.
You can fix it using:
select *
from bricks
CROSS JOIN (select count(*) AS cnt from bricks) counts
LEFT OUTER JOIN (
select colour, count(*) AS colour_cnt
from bricks
group by colour
) colours
on bricks.colour=colours.colour
Which, for the sample data:
CREATE TABLE bricks (id, colour) AS
SELECT 1, 'red' FROM DUAL UNION ALL
SELECT 2, 'red' FROM DUAL UNION ALL
SELECT 3, 'red' FROM DUAL UNION ALL
SELECT 4, 'green' FROM DUAL UNION ALL
SELECT 5, 'green' FROM DUAL UNION ALL
SELECT 6, 'blue' FROM DUAL;
Outputs:
ID
COLOUR
CNT
COLOUR
COLOUR_CNT
1
red
6
red
3
2
red
6
red
3
3
red
6
red
3
4
green
6
green
2
5
green
6
green
2
6
blue
6
blue
1
However, you probably want to simplify things and use the analytic COUNT() OVER (...) function and eliminate the self-joins:
select b.*,
COUNT(*) OVER () AS cnt,
COUNT(*) OVER (PARTITION BY colour) AS colour_cnt
from bricks b;
Which outputs:
ID
COLOUR
CNT
COLOUR_CNT
6
blue
6
1
5
green
6
2
4
green
6
2
2
red
6
3
1
red
6
3
3
red
6
3
(identical other than not duplicating the colours column)
db<>fiddle here

How to write a sql to dynamically add some calculated rows in Oracle?

I have a table like this:
id name value
1 elec 10
1 water 20
2 elec 15
2 water 45
Now I need to dynamically add some rows to the result of select query:
id name value
1 elec 10
1 water 20
1 ratio 0.5
2 elec 15
2 water 45
2 ratio 0.33
Add two rows dynamically,how can i do?
It would make a lot more sense to "present" the results with ELEC, WATER and RATIO columns - one row per ID. The solution below shows how you can do that efficiently (reading the base table only one time).
with
inputs ( id, name, value ) as (
select 1, 'elec' , 10 from dual union all
select 1, 'water', 20 from dual union all
select 2, 'elec' , 15 from dual union all
select 2, 'water', 45 from dual
)
-- End of simulated inputs (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select id, elec, water, round(elec/water, 2) as ratio
from inputs
pivot ( min(value) for name in ('elec' as elec, 'water' as water ) )
;
ID ELEC WATER RATIO
---------- ---------- ---------- ----------
1 10 20 .5
2 15 45 .33
If instead you need the results in the format you showed in your original post, you can unpivot like so (still reading the base table only once):
with
inputs ( id, name, value ) as (
select 1, 'elec' , 10 from dual union all
select 1, 'water', 20 from dual union all
select 2, 'elec' , 15 from dual union all
select 2, 'water', 45 from dual
)
-- End of simulated inputs (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select id, name, value
from (
select id, elec, water, round(elec/water, 2) as ratio
from inputs
pivot ( min(value) for name in ('elec' as elec, 'water' as water ) )
)
unpivot ( value for name in (elec as 'elec', water as 'water', ratio as 'ratio') )
;
ID NAME VALUE
---------- ----- ----------
1 elec 10
1 water 20
1 ratio .5
2 elec 15
2 water 45
2 ratio .33
Here is one method:
with t as (
<your query here>
)
select id, name, value
from ((select t.*, 1 as ord
from t
) union all
(select id, 'ratio',
max(case when name = 'elec' then value end) / max(case when name = 'water' then value end)
), 2 as ord
from t
group by id
)
) tt
order by id, ord;
If you are fine with slight change in ordering, try this.
SELECT id,name,value FROM yourtable
UNION ALL
SELECT
a.id ,
'ratio' name,
a.value/b.value value
FROM
yourtable a
JOIN yourtable b on a.id = b.id
WHERE a.name = 'elec'
and b.name = 'water'
ORDER BY
id ,
VALUE DESC;
If you need to add the rows to table itself, then use.
INSERT INTO yourtable
SELECT
a.id ,
'ratio' name,
a.value/b.value value
FROM
yourtable a
JOIN yourtable b on a.id = b.id
WHERE a.name ='elec'
and b.name ='water';

Categorizing in select statement

So i have this table
id | object | type
--------------------------------
1 | blue | color
1 | burger | food
2 | sandwich | food
2 | red | color
2 | coke | beverage
3 | sprite | beverage
3 | coke | beverage
3 | red | color
4 | bacon | food
i have to create a select statement that will show a table with columns id, color, food and beverage. Arranged by ID with their designated things on it.
so my expected result is
id | color | food | beverage
-------------------------------------------
1 | blue | burger |
2 | red | sandwich | coke
3 | red | | sprite
3 | | | coke
4 | | bacon |
as of now i have this code
Select id as id,
Case When I.Type = 'color' Then I.Object End As color,
Case When I.Type = 'food' Then I.Object End As food,
Case When I.Type = 'beverage' Then I.Object End As beverage
From table I
order by id
but the problem with my code is it doesnt group by its ID so it creates multiple rows for every object.
TIA!
You are looking for a pivot query. What is challenging about your problem is that, for a given id and type, there can be more than one object present. To handle this, you can first do a GROUP BY query to CSV aggregate objects for a given type using LISTAGG:
SELECT id,
MAX(CASE WHEN t.type = 'color' THEN t.object ELSE NULL END) AS color,
MAX(CASE WHEN t.type = 'food' THEN t.object ELSE NULL END) AS food,
MAX(CASE WHEN t.type = 'beverage' THEN t.object ELSE NULL END) AS beverage
FROM
(
SELECT id,
LISTAGG(object, ',') WITHIN GROUP (ORDER BY object) AS object,
type
FROM yourTable
GROUP BY id, type
) t
GROUP BY t.id
The inner query first aggregates objects across both id and type, and the outer query is a simple pivot query as you might expect.
Here is a Fiddle which shows an almost identical query in MySQL (Oracle seems to be perpetually broken):
SQLFiddle
You can try with something like the following:
with test(id, object, type) as
(
select 1,'blue', 'color' from dual union all
select 1,'burger', 'food' from dual union all
select 2,'sandwich','food' from dual union all
select 2,'red', 'color' from dual union all
select 2,'coke', 'beverage' from dual union all
select 3,'sprite', 'beverage' from dual union all
select 3,'coke', 'beverage' from dual union all
select 3,'red', 'color' from dual union all
select 4,'bacon', 'food' from dual
)
select id,
max( case when type = 'color'
then object
else null
end
) as color,
max( case when type = 'food'
then object
else null
end
) as food,
max( case when type = 'beverage'
then object
else null
end
) as beverage
from (
select id, object, type, row_number() over ( partition by id, type order by object) row_for_id
from test
)
group by id, row_for_id
order by id, row_for_id
The inner query is the main part, where you handle the case of a single id with many objects of a type; you can modify the ordering by editing the order by object.
The external query can be re-written in different ways, for example with a PIVOT; i used the MAX hoping to make it clear.
Try this
I have achieved using pivot clause
select id,object,type from yourtable
pivot
(
LISTAGG(object, ',') WITHIN GROUP (ORDER BY object)
for type IN
(
'color' AS "color",
'food' AS "food",
'beverage' AS "beverage"
)
)
order by id
Robin: My comment to you (under Tim Biegeleisen's answer) is partially incorrect. There IS a pivot-based solution; however, the "groups" are not by id, but instead they are by id AND rank within your three "categories". For this solution (or ANY solution that does not use dynamic SQL) to work, it is necessary that all the "types" (and their names) be known beforehand, and they must be hardcoded in the SQL query.
NOTE: In this solution, I assumed that for each id, the "objects" within the same "type" are associated to each other based on their alphabetical order (so, for example, for id = 3, "coke" is associated with "red" and "sprite" is associated with NULL, unlike your sample output). I asked you about that right below your Question - if there are additional rules you did not share with us, requiring a different pairing of objects of different types, it may or may not be possible to adapt the solution to meet those additional rules.
EDIT: On closer look, this is pretty much what Aleksej provided, without using the explicit pivot syntax. His solution has the advantage that it would work in older versions of Oracle (before 11.1 where pivot first became available).
QUERY (including test data in the first CTE):
with
inputs ( id, object, type ) as (
select 1, 'blue' , 'color' from dual union all
select 1, 'burger' , 'food' from dual union all
select 2, 'sandwich' , 'food' from dual union all
select 2, 'red' , 'color' from dual union all
select 2, 'coke' , 'beverage' from dual union all
select 3, 'sprite' , 'beverage' from dual union all
select 3, 'coke' , 'beverage' from dual union all
select 3, 'red' , 'color' from dual union all
select 4, 'bacon' , 'food' from dual
),
r ( id, object, type, rn ) as (
select id, object, type, row_number() over (partition by id, type order by object)
from inputs
)
select id, color, food, beverage
from r
pivot ( max(object) for type in ( 'color' as color, 'food' as food,
'beverage' as beverage))
order by id, rn
;
OUTPUT:
ID COLOR FOOD BEVERAGE
---- -------- -------- --------
1 blue burger
2 red sandwich coke
3 red coke
3 sprite
4 bacon

Get distinct rows based on priority?

I have a table as below.i am using oracle 10g.
TableA
------
id status
---------------
1 R
1 S
1 W
2 R
i need to get distinct ids along with their status. if i query for distinct ids and their status i get all 4 rows.
but i should get only 2. one per id.
here id 1 has 3 distinct statuses. here i should get only one row based on priority.
first priority is to 'S' , second priority to 'W' and third priority to 'R'.
in my case i should get two records as below.
id status
--------------
1 S
2 R
How can i do that? Please help me.
Thanks!
select
id,
max(status) keep (dense_rank first order by instr('SWR', status)) as status
from TableA
group by id
order by 1
fiddle
select id , status from (
select TableA.*, ROW_NUMBER()
OVER (PARTITION BY TableA.id ORDER BY DECODE(
TableA.status,
'S',1,
'W',2,
'R',3,
4)) AS row_no
FROM TableA)
where row_no = 1
This is first thing i would do, but there may be a better way.
Select id, case when status=1 then 'S'
when status=2 then 'W'
when status=3 then 'R' end as status
from(
select id, max(case when status='S' then 3
when status='W' then 2
when status='R' then 1
end) status
from tableA
group by id
);
To get it done you can write a similar query:
-- sample of data from your question
SQL> with t1(id , status) as (
2 select 1, 'R' from dual union all
3 select 1, 'S' from dual union all
4 select 1, 'W' from dual union all
5 select 2, 'R' from dual
6 )
7 select id -- actual query
8 , status
9 from ( select id
10 , status
11 , row_number() over(partition by id
12 order by case
13 when upper(status) = 'S'
14 then 1
15 when upper(status) = 'W'
16 then 2
17 when upper(status) = 'R'
18 then 3
19 end
20 ) as rn
21 from t1
22 ) q
23 where q.rn = 1
24 ;
ID STATUS
---------- ------
1 S
2 R
select id,status from
(select id,status,decode(status,'S',1,'W',2,'R',3) st from table) where (id,st) in
(select id,min(st) from (select id,status,decode(status,'S',1,'W',2,'R',3) st from table))
Something like this???
SQL> with xx as(
2 select 1 id, 'R' status from dual UNION ALL
3 select 1, 'S' from dual UNION ALL
4 select 1, 'W' from dual UNION ALL
5 select 2, 'R' from dual
6 )
7 select
8 id,
9 DECODE(
10 MIN(
11 DECODE(status,'S',1,'W',2,'R',3)
12 ),
13 1,'S',2,'W',3,'R') "status"
14 from xx
15 group by id;
ID s
---------- -
1 S
2 R
Here, logic is quite simple.
Do a DECODE for setting the 'Priority', then find the MIN (i.e. one with Higher Priority) value and again DECODE it back to get its 'Status'
Using MOD() example with added values:
SELECT id, val, distinct_val
FROM
(
SELECT id, val
, ROW_NUMBER() OVER (ORDER BY id) row_seq
, MOD(ROW_NUMBER() OVER (ORDER BY id), 2) even_row
, (CASE WHEN id = MOD(ROW_NUMBER() OVER (ORDER BY id), 2) THEN NULL ELSE val END) distinct_val
FROM
(
SELECT 1 id, 'R' val FROM dual
UNION
SELECT 1 id, 'S' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
UNION
SELECT 2 id, 'R' val FROM dual
UNION -- comment below for orig data
SELECT 3 id, 'K' val FROM dual
UNION
SELECT 4 id, 'G' val FROM dual
UNION
SELECT 1 id, 'W' val FROM dual
))
WHERE distinct_val IS NOT NULL
/
ID VAL DISTINCT_VAL
--------------------------
1 S S
2 R R
3 K K
4 G G

Replace beginning words

I have the below tables.
tblInput
Id WordPosition Words
-- ----------- -----
1 1 Hi
1 2 How
1 3 are
1 4 you
2 1 Ok
2 2 This
2 3 is
2 4 me
tblReplacement
Id ReplacementWords
--- ----------------
1 Hi
2 are
3 Ok
4 This
The tblInput holds the list of words while the tblReplacement hold the words
that we need to search in the tblInput and if a match is found then we need to replace
those.
But the problem is that, we need to replace those words if any match is found at the beginning.
i.e. in the tblInput,
in case of ID 1, the words that will be replaced is only 'Hi' and not 'are'
since before 'are', 'How' is there and it is not in the tblReplacement list.
in case of Id 2, the words that will be replaced are 'Ok' & 'This'. Since these both
words are present in the tblReplacement table and after the first word i.e. 'Ok' is
replaced, the second word which is 'This' here comes first in the list of
ID category 2
. Since it is available in the tblReplacement, and is the first word now, so this will
also be replaced.
So the desired output will be
Id NewWordsAfterReplacement
--- ------------------------
1 How
1 are
1 you
2 is
2 me
My approach so far:
;With Cte1 As(
Select
t1.Id
,t1.Words
,t2.ReplacementWords
From tblInput t1
Cross Join tblReplacement t2)
,Cte2 As(
Select Id, NewWordsAfterReplacement = REPLACE(Words,ReplacementWords,'')
From Cte1)
Select * from Cte2 where NewWordsAfterReplacement <> ''
But I am not getting the desired output. It is replacing all the matching words.
Urgent help needed**.( SET BASED )**
I am using SQL Server 2005.
Thanks
I couldnt exactly understand your requirement.But from what I understand this works for the given data
with cte1 as(
select 1 as id, 1 as wp, 'Hi' as words union all
select 1, 2 , 'How' union all
select 1 , 3 , 'are' union all
select 1 , 4 , 'you' union all
select 2 , 1 , 'Ok' union all
select 2 , 2 , 'This' union all
select 2 , 3 , 'is' union all
select 2 , 4 , 'me'
),
cte2 as(
select 1 as id, 'Hi' as rep union all
select 2 , 'are' union all
select 3 , 'Ok' union all
select 4 , 'This')
select ID,words from cte1
except
select ID,words from(
select a.id,a.words,a.wp,RANK() over(partition by a.id order by wp) as rnk
from cte1 as a inner join
cte2 as b on a.words=b.rep
) as x
where x.wp=x.rnk