using oracle regexp_replace to replace some part of a code - sql

I have some codes and I want to replace a peace of that code only. in my case BB to XX
AA/BB
AA/BB1
AA/BB-1
BB
BB1
BB-1
I tried use to regexp_replace with this simple form
Query:
select regexp_replace('AA/BB','BB','XX') from dual;
Result:
AA/XX
Query:
select regexp_replace('AA/BB-1','BB','XX') from dual;
Result:
AA/XX-1
It works fine but it can happen that before the slash AA will be BB as well but this time it shouldn't be replaced but still works for the rest of codes.
select regexp_replace('BB/BB','BB','XX') from dual;
gives me XX/XX of course but I want to achieve BB/XX etc.

Alternatively - see comments within code, where "starting at position" reads as: if there are more than 1 BB substrings there, start at position of the 2nd BB within the MYVAL. Otherwise, start from the beginning of MYVAL.
Thank you, #GMB, for sample data.
SQL> with t as (
2 select 'AA/BB' myval from dual
3 union all select 'AA/BB1' from dual
4 union all select 'AA/BB-1' from dual
5 union all select 'BB' from dual
6 union all select 'BB' from dual
7 union all select 'BB1' from dual
8 union all select 'BB-1' from dual
9 union all select 'BB/BB' from dual
10 union all select 'AA/BB/BB-2' from dual
11 )
12 select myval,
13 regexp_replace
14 (myval, --> in MYVAL
15 'BB', --> replace BB
16 'XX', --> with XX
17 case when regexp_count(myval, 'BB') = 1 then 1 --> starting at position*
18 else instr(myval, 'BB', 1, 2)
19 end
20 ) result
21 from t;
MYVAL RESULT
---------- ---------------
AA/BB AA/XX
AA/BB1 AA/XX1
AA/BB-1 AA/XX-1
BB XX
BB XX
BB1 XX1
BB-1 XX-1
BB/BB BB/XX
AA/BB/BB-2 AA/BB/XX-2
9 rows selected.
SQL>

Maybe we could phrase this as: replace 'BB' that is not followed by '/'?
regexp_replace(myval, 'BB($|[^/])', 'XX\1')
Demo on DB Fiddle:
with t as (
select 'AA/BB' myval from dual
union all select 'AA/BB1' from dual
union all select 'AA/BB-1' from dual
union all select 'BB' from dual
union all select 'BB' from dual
union all select 'BB1' from dual
union all select 'BB-1' from dual
union all select 'BB/BB' from dual
)
select myval, regexp_replace(myval, 'BB($|[^/])', 'XX\1') newval from t
MYVAL | NEWVAL
:------ | :------
AA/BB | AA/XX
AA/BB1 | AA/XX1
AA/BB-1 | AA/XX-1
BB | XX
BB | XX
BB1 | XX1
BB-1 | XX-1
BB/BB | BB/XX

Related

Oracle filter groups that contains more than one row condition

I need a SQL query for Oracle that select groups that contains the elements "ABC" and "ANQ"
group X Column Q
-------- ---------
123 ABC
123 AAA
123 ANQ
456 ANQ
456 PKR
579 AAA
579 XYZ
886 ABC
The desired result should be
group X Column Q
-------- ---------
123 ABC
123 AAA
123 ANQ
You can query the table only once by using the analytic COUNT function with conditional aggregation:
SELECT x,
q
FROM (
SELECT x,
q,
COUNT(CASE q WHEN 'ABC' THEN 1 END) OVER (PARTITION BY x) AS num_abc,
COUNT(CASE q WHEN 'ANQ' THEN 1 END) OVER (PARTITION BY x) AS num_anq
FROM table_name
)
WHERE num_abc > 0
AND num_anq > 0;
Which, for the sample data:
CREATE TABLE table_name (X, Q) AS
SELECT 123, 'ABC' FROM DUAL UNION ALL
SELECT 123, 'AAA' FROM DUAL UNION ALL
SELECT 123, 'ANQ' FROM DUAL UNION ALL
SELECT 456, 'ANQ' FROM DUAL UNION ALL
SELECT 456, 'PKR' FROM DUAL UNION ALL
SELECT 579, 'AAA' FROM DUAL UNION ALL
SELECT 579, 'XYZ' FROM DUAL UNION ALL
SELECT 886, 'ABC' FROM DUAL;
Outputs:
X
Q
123
ABC
123
AAA
123
ANQ
fiddle
For example:
Sample data:
SQL> with test (x, q) as
2 (select 123, 'abc' from dual union all
3 select 123, 'aaa' from dual union all
4 select 123, 'anq' from dual union all
5 select 456, 'anq' from dual union all
6 select 456, 'pkr' from dual union all
7 select 579, 'aaa' from dual union all
8 select 579, 'xyz' from dual union all
9 select 886, 'abc' from dual
10 )
Query:
11 select x, q
12 from test a
13 where exists (select null
14 from test b
15 where b.q in ('abc', 'anq')
16 and b.x = a.x
17 group by b.x
18 having count(distinct b.q) = 2
19 );
X Q
---------- ---
123 abc
123 aaa
123 anq
SQL>

ORACLE Sort Alphabetically Upper A, Lower a, numerics

I am sorting with Oracle version 12 with the following NLS parameters
NLS_LANGUAGE AMERICAN
NLS_SORT BINARY
NLS_COMP BINARY
Eg. " 1Andy"," Andy","Andy","andy","Aaron","Bob"
SELECT name from employee order by name
result:
" 1Andy"," Andy","Aaron","Andy","Bob","andy"
if I change the NLS_SORT to WEST_EUROPEAN, it is suppose to be " 1Andy"," Andy","Aaron","Andy","andy","Bob"
but the result is " 1Andy"," Andy","andy","Aaron","Andy","Bob"
where andy even though is lower case goes in the middle.
SELECT name from employee order by NLSSORT(name ,'NLS_SORT=WEST_EUROPEAN')
enter image description here
To begin with, you can have case-insensitive sorting by adding _ci the the collation spec: west_european_ci. That being said, it seems in west_european sorting spec, number come after alpha chars. Here's what I get with your data using different collation specs
SQL> with dt as (
2 select ' 1Andy' cv from dual
3 union all
4 select ' Andy' from dual
5 union all
6 select '1Andy' from dual
7 union all
8 select 'Andy' from dual
9 union all
10 select 'andy' from dual
11 union all
12 select 'Aaron' from dual
13 union all
14 select 'Bob' from dual)
15 select *
16 from dt
17 order by cv;
CV
------
1Andy
Andy
1Andy
Aaron
Andy
Bob
andy
7 rows selected.
SQL>
SQL> with dt as (
2 select ' 1Andy' cv from dual
3 union all
4 select ' Andy' from dual
5 union all
6 select '1Andy' from dual
7 union all
8 select 'Andy' from dual
9 union all
10 select 'andy' from dual
11 union all
12 select 'Aaron' from dual
13 union all
14 select 'Bob' from dual)
15 select *
16 from dt
17 order by nlssort(cv,'NLS_SORT=WEST_EUROPEAN');
CV
------
Andy
1Andy
Aaron
Andy
andy
Bob
1Andy
7 rows selected.
SQL>
SQL> with dt as (
2 select ' 1Andy' cv from dual
3 union all
4 select ' Andy' from dual
5 union all
6 select '1Andy' from dual
7 union all
8 select 'Andy' from dual
9 union all
10 select 'andy' from dual
11 union all
12 select 'Aaron' from dual
13 union all
14 select 'Bob' from dual)
15 select *
16 from dt
17 order by nlssort(cv,'NLS_SORT=WEST_EUROPEAN_ci');
CV
------
Andy
1Andy
Aaron
Andy
andy
Bob
1Andy
7 rows selected.

How do I turn rows with duplicate values to columns?

I try to pivot my table but keeping additional rows (in my example eeeeee ) Is there a way in Oracle SQL to do this?
select * from (
select
mat_table.material, attribute_table.attribute, attribute_table.value
from
mat_table mat_table
inner join
attribute_table on mat_table.rel= attribute_table.rel
where
material = 'Material_A'
)
material |attribute| value
_____________________________________
Material_A |aaaaaa |
Material_A |bbbbbb | hello
Material_A |cccccc | val_1
Material_A |dddddd | 2
Material_A |eeeeee | 15
Material_A |eeeeee | 16
Material_A |eeeeee | 24
when I use pivot under the where clause
pivot (
max(attribute) as max_value for attribute IN ( 'aaaaaa',
'bbbbbb',
'cccccc',
'dddddd',
'eeeeee'
))
I am getting closer to what I want but for eeeee I get only one value
material |aaaaaa | bbbbbb | cccccc | dddddd | eeeeee |
__________________________________________________________
Material_A | | hello | val_1 | 2 | 24 |
but what I want is something like
material |aaaaaa | bbbbbb | cccccc | dddddd | eeeeee_1 | eeeeee_2 | eeeeee_3 |
__________________________________________________________________________________
Material_A | | hello | val_1 | 2 | 15 16 | 24
If there are always 3 values for eeeeee then you can do it as following
SQL> with mat_table (material, attribute, value) as
2 (
3 select 'Material_A', 'aaaaaa', null from dual
4 union all select 'Material_A', 'bbbbbb', 'hello' from dual
5 union all select 'Material_A', 'cccccc', 'val_1' from dual
6 union all select 'Material_A', 'dddddd', '2' from dual
7 union all select 'Material_A', 'eeeeee', '15' from dual
8 union all select 'Material_A', 'eeeeee', '16' from dual
9 union all select 'Material_A', 'eeeeee', '24' from dual
10 )
11 select *
12 from (select t.*,
13 row_number() over(partition by attribute order by value) rn
14 from mat_table t)
15 pivot (max(value) for (attribute, rn) in
16 (
17 ('aaaaaa', 1), ('bbbbbb', 1), ('cccccc', 1), ('dddddd', 1),
18 ('eeeeee', 1), ('eeeeee', 2), ('eeeeee', 3)
19 ));
MATERIAL 'aaaa 'bbbb 'cccc 'dddd 'eeee 'eeee 'eeee
---------- ----- ----- ----- ----- ----- ----- -----
Material_A hello val_1 2 15 16 24
If, however, you expect Oracle to dynamically create columns for any number of values for eeeeee then that is not possible.
Please read detailed explanation here Oracle Dynamic Pivoting
You can generate XML for any combinations of attribute and value but if you want to display result using SQL then eventually all the columns must be specified (alternative approach is parsing XML on client side).
SQL> with mat_table (material, attribute, value) as
2 (
3 select 'Material_A', 'aaaaaa', null from dual
4 union all select 'Material_A', 'bbbbbb', 'hello' from dual
5 union all select 'Material_A', 'cccccc', 'val_1' from dual
6 union all select 'Material_A', 'dddddd', '2' from dual
7 union all select 'Material_A', 'eeeeee', '15' from dual
8 union all select 'Material_A', 'eeeeee', '16' from dual
9 union all select 'Material_A', 'eeeeee', '24' from dual
10 )
11 select material, x.*
12 from mat_table
13 pivot xml (count(*) as dummy for (attribute, value) in (any, any))
14 -- parsing output
15 , xmltable('/PivotSet' passing attribute_value_xml
16 columns
17 aaaaaa varchar2(10) path '/PivotSet/item[column="aaaaaa"]/column[2]',
18 bbbbbb varchar2(10) path '/PivotSet/item[column="bbbbbb"]/column[2]',
19 cccccc varchar2(10) path '/PivotSet/item[column="cccccc"]/column[2]',
20 dddddd varchar2(10) path '/PivotSet/item[column="dddddd"]/column[2]',
21 eeeeee_1 varchar2(10) path '/PivotSet/item[column="eeeeee"][1]/column[2]',
22 eeeeee_2 varchar2(10) path '/PivotSet/item[column="eeeeee"][2]/column[2]',
23 eeeeee_3 varchar2(10) path '/PivotSet/item[column="eeeeee"][3]/column[2]') x;
MATERIAL AAAAAA BBBBBB CCCCCC DDDDDD EEEEEE_1 EEEEEE_2 EEEEEE_3
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
Material_A hello val_1 2 15 16 24
In this case there is no guarantee that EEEEEE_1/EEEEEE_2/EEEEEE_3 will be 15/16/24 in exactly this order.
EDIT: TS commented that it did not work for multiple materials. So I expanded the answer to account for that.
You could simply concat a row_number (partioned by material, attribute) to the attribute in your first query. You can add ordening by value if you like. To account for multiple materials the row_number to also partitioned by material. This means that the same attributes for different materials will get the same name and will end up in the same column.
replace attribute_table.attribute with
concat(attribute_table.attribute,'_', row_number() over (partition by attribute_table.material, attribute_table.attribute order by attribute_table.attribute, attribute_table.value))
Complete code and result:
with mat_table as
(
select 'Material_A' as material, 'aaaaaa' as attribute, null as value
union all select 'Material_A', 'bbbbbb', 'hello'
union all select 'Material_A', 'cccccc', 'val_1'
union all select 'Material_A', 'dddddd', '2'
union all select 'Material_A', 'eeeeee', '15'
union all select 'Material_A', 'eeeeee', '16'
union all select 'Material_A', 'eeeeee', '24'
union all select 'Material_B' , 'aaaaaa', 'lol'
union all select 'Material_B', 'bbbbbb', 'hi'
union all select 'Material_B', 'cccccc', 'max_val'
union all select 'Material_B', 'dddddd', '4'
union all select 'Material_B', 'eeeeee', '67'
union all select 'Material_B', 'eeeeee', '99'
union all select 'Material_B', 'eeeeee', null
)
select *
from (
select t.material,
t.value ,
concat(t.attribute,'_', row_number() over (partition by t.material , t.attribute order by t.attribute, t.value)) as numbered_attribute
from mat_table t) as d
pivot (
max(d.value)
for numbered_attribute IN ( [aaaaaa_1],
[bbbbbb_1],
[cccccc_1],
[dddddd_1],
[eeeeee_1],
[eeeeee_2],
[eeeeee_3]
)) as total
order by total.material
Note: I used SQL-Server. Maybe you'll have to change some syntax like [eeeeee_3] => 'eeeeee_3'
Base table with numbered attributes:
Final result after pivot:

How to sort alphanumeric String in oracle?

Input is:
Section1
Section2
Section3
Section10
Section11
Section1A
Section1B
Section12
Section11A
Section11B
And I want output like:
Section1
Section1A
Section1B
Section2
Section3
Section10
Section11
Section11A
Section11B
Section12
I tried query :
select section_name
from sections
order by length(section_name),section_name
Assuming that the structure of your strings is fixed, as in your example, this could be a way:
SQL> select x,
2 to_number(regexp_substr(x, '[0-9]+')) numericPart,
3 regexp_substr(x, '([0-9]+)([A-Z])', 1, 1, '', 2) optionalChar
4 from (
5 select 'Section1' x from dual union all
6 select 'Section2' from dual union all
7 select 'Section3' from dual union all
8 select 'Section10' from dual union all
9 select 'Section11' from dual union all
10 select 'Section1A' from dual union all
11 select 'Section1B' from dual union all
12 select 'Section12' from dual union all
13 select 'Section11A' from dual union all
14 select 'Section11B' from dual
15 )
16 order by numericPart,
17 optionalChar nulls first
18 ;
X NUMERICPART OPTIONALCHAR
---------- ----------- ----------------------------------------
Section1 1
Section1A 1 A
Section1B 1 B
Section2 2
Section3 3
Section10 10
Section11 11
Section11A 11 A
Section11B 11 B
Section12 12
Here you first order by the numeric part, treating it as number, and then consider the (optional) character after the number.

Oracle SQL : Regexp_substr

I have below sample values in a column
Abc-123-xyz
Def-456-uvw
Ghi-879-rst-123
Jkl-abc
Expected output is the third element split by '-', in case there is no third element, the last element will be retrieve.
See expected output below:
Xyz
Uvw
Rst
Abc
Thanks ahead for the help.
SELECT initcap(nvl(regexp_substr(word, '[^-]+', 1,3),regexp_substr(word, '[^-]+', 1,2))) FROM your_table;
Another approach:
SQL> with t1(col) as(
2 select 'Abc-123-xyz' from dual union all
3 select 'Def-456-uvw' from dual union all
4 select 'Ghi-879-rst-123' from dual union all
5 select 'Jkl-Abc' from dual
6 )
7 select regexp_substr( col
8 , '[^-]+'
9 , 1
10 , case
11 when regexp_count(col, '[^-]+') >= 3
12 then 3
13 else regexp_count(col, '[^-]+')
14 end
15 ) as res
16 from t1
17 ;
Result:
RES
---------------
xyz
uvw
rst
Abc
regexp_substr(column, '(.*?-){0,2}([^-]+)', 1, 1, '', 2)
You can also do it without RegEx:
with t1 as(
select 'Abc-123-xyz' as MyText from dual union all
select 'Def-456-uvw' from dual union all
select 'Ghi-879-rst-123' from dual union all
select 'Jkl-Abc' from dual
)
SELECT
SUBSTR(t1.mytext, LENGTH(t1.mytext) - INSTR(REVERSE(t1.mytext), '-') + 2)
FROM t1
;