select distinct column, priority second column value - oracle

I want to select distinct/group by column and show for the second column only one value(out of 3 possible) priority by: if A appears in the group show it, if not show B, if not show C.
This is the table:
A B
---- -----
FST A
FST B
FST C
INCS C
INCS B
ASW A
AWR C
WER C
WER C
WER B
RESULT
A B
---- -----
FST A
INCS B
ASW A
AWR C
WER B

For your given data, the easiest way is:
select A, min(B)
from table t
group by A;
For the more general problem where alphabetic ordering is not correct (if you wanted to return 'B' ahead of 'A', say), you can do this with case statements:
select A,
(case min(case B when 'A' then 1 when 'B' then 2 when 'C' then 3 else 4 end)
when 1 then 'A'
when 2 then 'B'
when 3 then 'C'
else '???'
end)
from table t
group by A;
EDIT:
Oracle has an easier way to do this than the nested selects:
select A,
max(B) keep (dense_rank first
order by (case B when 'A' then 1 when 'B' then 2 when 'C' then 3 else 4 end)
) as BestB
from table t
group by A;

Related

Oracle: How functional table works in cross join?

I explored technique to unfold comma separated list in column into rows:
with tbl as (
select 1 id, 'a,b' lst from dual
union all select 2 id, 'c' lst from dual
union all select 3 id, 'e,f,g' lst from dual)
select
tbl.ID
, regexp_substr(tbl.lst, '[^,]+', 1, lvl.column_value) elem
, lvl.column_value lvl
from
tbl
, table(cast(multiset(
select level from dual
connect by level <= regexp_count(tbl.lst, ',')+1) as sys.odcinumberlist)) lvl;
Result is:
ID ELEM LVL
1 a 1
1 b 2
2 c 1
3 e 1
3 f 2
3 g 3
As you can see LVL depends on value of regexp_count, so second functional table in cross join is parametrized by first table.
How is it working? How is it called? Can I paramertize third table based on two preceding in cross join and so forth?
Is parametrization limited to cross join or can be applied in join syntax too?
Reference: Splitting string into multiple rows in Oracle
From the documentation:
LATERAL
Specify LATERAL to designate subquery as a lateral inline
view. Within a lateral inline view, you can specify tables that appear
to the left of the lateral inline view in the FROM clause of a query.
You can specify this left correlation anywhere within subquery (such
as the SELECT, FROM, and WHERE clauses) and at any nesting level.
-- a variation of the query in your question ...
select
dt.id
, dt.list
, regexp_substr( dt.list, '[^,]+', 1, dt2.lvl ) elements
, dt2.lvl
from (
select 1 id, 'a,b' list from dual union all
select 2, 'c' from dual union all
select 3, 'e,f,g' from dual
) dt, lateral (
select level lvl from dual
connect by level <= regexp_count(dt.list, ',') + 1
) dt2
;
-- output
ID LIST ELEMENTS LVL
1 a,b a 1
1 a,b b 2
2 c c 1
3 e,f,g e 1
3 e,f,g f 2
3 e,f,g g 3
Example with 3 tables:
--drop table t1 ;
--drop table t2 ;
--drop table t3 ;
-- tables/data
create table t1
as
select 1 id, 'a' letter from dual union all
select 2, 'b' from dual union all
select 3, 'c' from dual ;
create table t2
as
select 1 id, 'd' letter from dual union all
select 2, 'e' from dual union all
select 3, 'f' from dual ;
create table t3
as
select 1 id, 'g' letter from dual union all
select 2, 'h' from dual union all
select 3, 'i' from dual ;
-- query
select *
from
t1
, lateral ( select letter from t2 where id = t1.id ) t2
, lateral ( select letter from t3 where id = t2.id )
;
-- output
ID LETTER LETTER LETTER
1 a d g
2 b e h
3 c f i
Also (using the same tables)
-- reference t1 <- t2,
-- reference t1 and t2 <- t3
select *
from
t1
, lateral ( select letter from t2 where id = t1.id ) t2
, lateral ( select letter || t1.letter from t3 where id = t2.id )
;
-- output
ID LETTER LETTER LETTER||T1.LETTER
1 a d ga
2 b e hb
3 c f ic
Whereas a "standard" cross join would give us ...
select *
from
t1 cross join t2 cross join t3
;
ID LETTER ID LETTER ID LETTER
1 a 1 d 1 g
1 a 1 d 2 h
1 a 1 d 3 i
1 a 2 e 1 g
1 a 2 e 2 h
1 a 2 e 3 i
...
-- 27 rows
Related topics: CROSS APPLY (see documentation and examples here).

Oracle: prioritizing results based on column’s value

I have a data-set in which there are duplicate IDs in the first column. I'm hoping to obtain a single row of data for each ID based on the second column's value. The data looks like so:
ID Info_Source Prior?
A 1 Y
A 3 N
A 2 Y
B 1 N
B 1 N
B 2 Y
C 2 N
C 3 Y
C 1 N
Specifically the criteria would call for prioritizing based on the second column's value (3 highest priority; then 1; and lastly 2): if the 'Info_Source' column has a value of 3, return that row; if there is no 3 in the second column for a given ID, look for a 1 and if found return that row; and finally if there is no 3 or 1 associated with the ID, search for 2 and return that row for the ID.
The desired results would be a single row for each ID, and the resulting data would be:
ID Info_Source Prior?
A 3 N
B 1 N
C 3 Y
row_number() over() usually solves these needs nicely and efficiently e.g.
select ID, Info_Source, Prior
from (
select ID, Info_Source, Prior
, row_number() over(partition by id order by Info_source DESC) as rn
)
where rn = 1
For prioritizing the second column's value (3 ; then 1, then 2) use a case expression to alter the raw value into an order that you need.
select ID, Info_Source, Prior
from (
select ID, Info_Source, Prior
, row_number() over(partition by id
order by case when Info_source = 3 then 3
when Infor_source = 1 then 2
else 1 end DESC) as rn
)
where rn = 1

How to use comma separated value from column to IN clause [duplicate]

This question already has answers here:
Using the "IN" clause with a comma delimited string from the output of a replace() function in Oracle SQL
(6 answers)
Closed 6 years ago.
I have a table, which contains below values(small snap of data):
Row#| Column A | Column B
--------------------------
1 |A | A,B
2 |C | A,D
3 |M | Z
4 |C |A,B,L
5 |F |F
6 |C |O
List goes on and on. Column A and B is populated by other application, so its combinations are not predictable. Table has more than 4000 rows.
My search values are not fixed; I have to use search value from Column B of same row for which I am looking for data in Column A.
I want to check Column A's value is IN Column B or not. Basically compare Column A with B, if find match in B result should skip that row.
In above case Row#1's Column A's value is in B which is not true for row#2.
My query should reach all rows and compare Column A with B. Query should return Row#1 and 5
This question is different Using the "IN" clause with a comma delimited string from the output of a replace() function in Oracle SQL
As it has fixed set of search value
select * from my_table where instr(columnB, columnA) > 0;
Example: Should contain
select * from (
select 1 a, 'A' b, 'A,B' c from dual union all
select 2, 'C', 'A,D' from dual ) t
where instr(c,b) > 0;
Result:
A B C
-----------
1 A A,B
Example 2: Shouldn't contain
select * from (
select 1 a, 'A' b, 'A,B' c from dual union all
select 2, 'C', 'A,D' from dual ) t
where instr(c,b) = 0;
Result:
A B C
-----------
2 C A,D

Update with group by

I'm stumped on what seemed to be a simple UPDATE statement.
I'm looking for an UPDATE that uses two values. The first (a) is used to group, the second (b) is used to find a local minimum of values within the respective group. As a little extra there is a threshold value on b: Any value 1 or smaller shall remain as it is.
drop table t1;
create table t1 (a number, b number);
insert into t1 values (1,0);
insert into t1 values (1,1);
insert into t1 values (2,1);
insert into t1 values (2,2);
insert into t1 values (3,1);
insert into t1 values (3,2);
insert into t1 values (3,3);
insert into t1 values (4,1);
insert into t1 values (4,3);
insert into t1 values (4,4);
insert into t1 values (4,5);
-- 1,0 -> 1,0
-- 1,1 -> 1,1
-- 2,1 -> 2,1
-- 2,2 -> 2,2
-- 3,1 -> 3,1
-- 3,2 -> 3,2
-- 3,3 -> 3,2 <-
-- 4,1 -> 4,1
-- 4,3 -> 4,3 <-
-- 4,4 -> 4,3 <-
-- 4,5 -> 4,3 <-
Obviously not sufficient is:
update t1 x
set b = (select min(b) from t1 where b > 1)
;
Whatever more complicated stuff I try, e.g.
UPDATE t1 x
set (a,b) = (select distinct a,b from (
select a, min(b) from t1 where b > 1 group by a)
)
;
I get
SQL-Fehler: ORA-01427: Unterabfrage für eine Zeile liefert mehr als eine Zeile
01427. 00000 - "single-row subquery returns more than one row"
which is not overly surprising as I need a row for each value of a.
Of course I could write a PL/SQL Procedure with a cursor loop but is it possible in a single elegant SQL statement? Maybe using partition by?
Your question is a bit confusing.
You say that you would like to set value b to a minimum value from partition a that column b is in row with, while the rows containing b = 1 should remain untouched.
From what I can see in your question as comments (I assume it's your expected output) you also want to get the minimum value that follows 1 within a partition - so you basically want the minimum value of b that is greater than 1.
Below is SQL query that does this
UPDATE t1 alias
SET b = (
SELECT min(b)
FROM t1
WHERE alias.a = t1.a
AND t1.b > 1 -- this would get the minimum value higher than 1
GROUP BY a
)
WHERE alias.b > 1 -- update will not affect rows with b <= 1
Output after update
a | b
---+---
1 | 0
1 | 1
2 | 1
2 | 2
3 | 1
3 | 2
3 | 2
4 | 1
4 | 3
4 | 3
4 | 3

Generate List for label with barcode printing from tablem with number quantity of rows

I try to generate list of products for printing labels, but all of my attempt fail (with connect by level)!
My table:
CREATE TABLE LABELS
(
PRODUCT VARCHAR2(8 BYTE),
Q_ROWS NUMBER
);
Information in the table:
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('D', 3);
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('A', 1);
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('C', 4);
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('B', 2);
Expected Result in a oracle select
PRODUCT
A
B
B
C
C
C
C
D
D
D
Results: (1 row for A, 2 rows for B, 4 rows to C and 3 rows to D)
Can someone help me?
Use LEVEL to get a "table" that counts from 1 to the maximum number of rows:
SELECT LEVEL AS LabelNum
FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(Q_Rows) FROM Labels)
This will give you the following table:
LabelNum
--------
1
2
3
4
Next, join this to your LABELS table where LabelNum <= Q_Rows. Here's the whole query:
WITH Mult AS (
SELECT LEVEL AS LabelNum
FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(Q_Rows) FROM Labels)
)
SELECT Product
FROM Labels
INNER JOIN Mult ON LabelNum <= Q_Rows
ORDER BY Product, LabelNum
There's a working SQLFiddle here.
Finally, good job including the create/populate scripts :)
Another approach, using model clause:
select product
from labels
model
partition by (product)
dimension by (1 as indx)
measures(q_rows)
rules(
q_rows[for indx from 1 to q_rows[1] increment 1] = q_rows[1]
)
order by product
result:
PRODUCT
----------
A
B
B
C
C
C
C
D
D
D
SQLFiddle Demo

Resources