unpivot with a flag in Oracle - sql

I have this table structure
ID,SUPPLIER_GROUP1,SUPPLIER1,SUPPLIER_GROUP2,SUPPLIER2.
i want to unpivot and get
ID,SUPPLIER_GROUP,SUPPLIER,TYPE
so each supplier_group and supplier values come to the appropriate column and in TYPE column will be either 1 or 2 to see if the SUPPLIER_GROUP and SUPPLIER value was supplier1 or supplier2 .

Use UNPIVOT with multiple column groups:
SELECT *
FROM table_name
UNPIVOT (
(supplier_group, supplier) FOR type IN (
(supplier_group1, supplier1) AS 1,
(supplier_group2, supplier2) AS 2
)
);
Which, for the sample data:
CREATE TABLE table_name (ID,SUPPLIER_GROUP1,SUPPLIER1,SUPPLIER_GROUP2,SUPPLIER2) AS
SELECT 1, 'sg1.1', 's1.1', 'sg2.1', 's2.1' FROM DUAL UNION ALL
SELECT 2, 'sg1.2', 's1.2', 'sg2.2', 's2.2' FROM DUAL UNION ALL
SELECT 3, 'sg1.3', 's1.3', 'sg2.3', 's2.3' FROM DUAL
Outputs:
ID
TYPE
SUPPLIER_GROUP
SUPPLIER
1
1
sg1.1
s1.1
1
2
sg2.1
s2.1
2
1
sg1.2
s1.2
2
2
sg2.2
s2.2
3
1
sg1.3
s1.3
3
2
sg2.3
s2.3
db<>fiddle here

Related

Count distinct values separated by a comma

I have this table called test
id
my_list
1
aa//11, aa//34, ab//65
2
bb//43, bb//43, be//54
3
4
cc//76
I want to count the distinct values in my_list, where each item in the list is separated by a comma. In this case:
id=1 will have 3 distinct values
id=2 will have 2 distinct values as bb//43 as shown up twice, thus 2 distinct values
id=3 will have 0 distinct values as it as an empty list
id=4 will have 1 since there is only 1 item in the list
I want to do this in pure SQL and not using a custom made procedure. I tried with the statement below but it is showing 1.
SELECT id, COUNT(DISTINCT my_list) as my_count
FROM test;
Expected result:
id
my_count
1
3
2
2
3
0
4
1
You need to turn your list into table to count distinct inside it. With json_table, for example.
with a(id, my_list) as (
select 1, 'aa//11, aa//34, ab//65' from dual union all
select 2, 'bb//43, bb//43, be//54' from dual union all
select 3, null from dual union all
select 4, 'cc//76' from dual
)
select
id
, (
select count(distinct val)
from json_table(
/*Replace comma with quotes and comma: ','
And wrap with array brackets
*/
'[''' || regexp_replace(my_list, '\s*,\s*', ''',''') || ''']'
, '$[*]'
columns ( val varchar(20) path '$')
)
) as cnt
from a
ID | CNT
-: | --:
1 | 3
2 | 2
3 | 0
4 | 1
db<>fiddle here

case statement after where clause to omit the row of data if satisfied

Hi I have a table as below and I'm trying to extract the data from them if and only if the below condition is satisfied.
ID Rank
45689 1
54789 2
98765 1
96541 2
98523 3
92147 4
96741 2
99999 10
If the ID starts with 4 and 9 or 5 and 9 and have same Rank then omit them. If ID starts with 9 and no matching Rank with other ID (starting with 4 or 5) then show them as result.
So My Output should look like
ID Rank
98523 3
92147 4
99999 10
How can I use case statement in where clause to filter the data?
If I understand correctly, you want to select only those ID that begin with a 9, and have a rank that is not also the rank of (another) ID that begins with 4 or 5. Is that correct?
The query below is for the case ID is of string data type (although it will work OK, probably, if ID is numeric data type - through implicit conversion).
select *
from your_table
where id like '9%'
and rank not in (
select rank
from your_table
where substr(id, 1, 1) in ('4', '5')
)
;
One option would be using COUNT() analytic function along with a conditional aggregation such as
WITH t2 AS
(
SELECT SUM(CASE WHEN SUBSTR(id,1,1) IN ('5','9') OR
SUBSTR(id,1,1) IN ('4','9') THEN 1 END ) OVER
(PARTITION BY Rank) AS count, t.*
FROM t -- your original table
)
SELECT id, rank
FROM t2
WHERE count = 1
Demo
You can use an analytic function to only query the table once:
SELECT id,
rank
FROM (
SELECT t.*,
COUNT( CASE WHEN id LIKE '4%' OR id LIKE '5%' THEN 1 END )
OVER ( PARTITION BY Rank )
AS num_match
FROM table_name t
WHERE id LIKE '4%'
OR id LIKE '5%'
OR id LIKE '9%'
)
WHERE id LIKE '9%'
AND num_match = 0;
Which, for the sample data:
CREATE TABLE table_name ( ID, Rank ) AS
SELECT 45689, 1 FROM DUAL UNION ALL
SELECT 54789, 2 FROM DUAL UNION ALL
SELECT 98765, 1 FROM DUAL UNION ALL
SELECT 96541, 2 FROM DUAL UNION ALL
SELECT 98523, 3 FROM DUAL UNION ALL
SELECT 92147, 4 FROM DUAL UNION ALL
SELECT 96741, 2 FROM DUAL UNION ALL
SELECT 99999, 10 FROM DUAL;
Outputs:
ID | RANK
----: | ---:
98523 | 3
92147 | 4
99999 | 10
db<>fiddle here

Query to delete duplicate records by keeping original in oracle

This is the table.
Id. Name
1 A
1 A
2 B
2 C
1 A
2 B
2 D
The output should be
Id. Name
1 A
2 B
2 C
2 D
please try
Select distinct id, name
from <name of you table>
order by name
Check this link.
Sample data:
create table demo (id, name) as
select 1, 'A' from dual union all
select 1, 'A' from dual union all
select 2, 'B' from dual union all
select 2, 'C' from dual union all
select 1, 'A' from dual union all
select 2, 'B' from dual union all
select 2, 'D' from dual;
select * from demo order by 1,2;
ID NAME
---------- ------------------------------
1 A
1 A
1 A
2 B
2 B
2 C
2 D
7 rows selected
Delete all but the first row in each (id, name) group:
delete demo where rowid in
( select lag(rowid) over (partition by id, name order by null) from demo );
3 rows deleted
select * from demo order by 1,2
ID N
---------- -
1 A
2 B
2 C
2 D
4 rows selected.

create query to get all components that refer to all selected ids of substances

Need your help to create some oracle SQL query, let's first see the design of my tables
Component table has two cols id(pk) and name
Substance table has two cols id(pk) and name
The Component consists of many substances so I created a third table
Comp_Subs has two cols comp_id(fk to Component) and sub_id(fk to substance) and the cols together are unique.
Given some ids of substances, Create a query to get all components that have all the given substances
Table Schema
Example: given these ids of substances 1, 2, 3 create a query that will retrieve all Comps that have all the selected substances.
input : 1,2,3
Output will one column contains the result like that
Output
The result of the given example should return comp1 and comp2
Because comp1 contains substances 1, 2 and 3
And comp2 also contains 1, 2 and 3
Use GROUP BY and HAVING:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Comp_Sub ( comp_id, sub_id ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 1, 3 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 2, 4 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 3, 5 FROM DUAL UNION ALL
SELECT 3, 7 FROM DUAL;
Query 1:
SELECT comp_id
FROM comp_sub
WHERE sub_id IN ( 1, 2, 3 )
GROUP BY comp_id
HAVING COUNT( sub_id ) = 3
Results:
| COMP_ID |
|---------|
| 1 |
| 2 |
If you want names then join the results to the Component table.
You can get the output by selecting the comp_subs records with the desired sub_id grouped by comp_id, like so:
select comp_id
from comp_subs
where subs_id in (1,2,3)
group by comp_id;

SQL find nearest number

Say I have a table like the following (I'm on Oracle 10g btw)
NAME VALUE
------ ------
BOB 1
BOB 2
BOB 4
SUZY 1
SUZY 2
SUZY 3
How can I select all rows where value is closest to, but not greater than, a given number. For example if I want to find all the rows where value is closest to 3 I would get:
NAME VALUE
------ ------
BOB 2
SUZY 3
This seems like it should be simple... but I'm having no luck.
Thanks!
SELECT name, max(value)
FROM tbl
WHERE value <= 3
GROUP BY name
This works (SQLFiddle demo):
SELECT name, max(value)
FROM mytable
WHERE value <= 3
GROUP BY name
Based on hagensofts answer:
SELECT name, max(value)
FROM tbl
WHERE value <= 3 AND ROWNUM <=2
GROUP BY name
With ROWNUM you can limit the output rows, so if you want 2 row, then you can limit the rownum.
WITH v AS (
SELECT 'BOB' NAME, 1 value FROM dual
UNION ALL
SELECT 'BOB', 2 FROM dual
UNION ALL
SELECT 'BOB', 4 FROM dual
UNION ALL
SELECT 'SUZY', 1 FROM dual
UNION ALL
SELECT 'SUZY', 2 FROM dual
UNION ALL
SELECT 'SUZY', 3 FROM dual
)
SELECT *
FROM v
WHERE (name, value) IN (SELECT name, MAX(value)
FROM v
WHERE value <= :num
GROUP BY name)
;