how to replace multiple strings together in Oracle - oracle

I have a string coming from a table like "can no pay{1},as your payment{2}due on {3}". I want to replace {1} with some value , {2} with some value and {3} with some value .
Is it Possible to replace all 3 in one replace function ? or is there any way I can directly write query and get replaced value ? I want to replace these strings in Oracle stored procedure the original string is coming from one of my table I am just doing select on that table
and then I want to replace {1},{2},{3} values from that string to the other value that I have from another table

Although it is not one call, you can nest the replace() calls:
SET mycol = replace( replace(mycol, '{1}', 'myoneval'), '{2}', mytwoval)

If there are many variables to replace and you have them in another table and if the number of variables is variable you can use a recursive CTE to replace them.
An example below. In table fg_rulez you put the strings with their replacement. In table fg_data you have your input strings.
set define off;
drop table fg_rulez
create table fg_rulez as
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'great than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '&', 'and' from dual;
drop table fg_data;
create table fg_Data AS(
SELECT 'amount $ must be < 1 & > 2' str FROM dual
union all
SELECT 'John is > Peter & has many $' str FROM dual
union all
SELECT 'Eliana is < mary & do not has many $' str FROM dual
);
WITH q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);
So, a single replace.
Result:
amount dollars must be less than 1 and great than 2
John is great than Peter and has many dollars
Eliana is less than mary and do not has many dollars
The terminology symbol instead of variable comes from this duplicated question.
Oracle 11gR2

Let's write the same sample as a CTE only:
with fg_rulez as (
select 1 id,'<' symbol, 'less than' text from dual
union all select 2, '>', 'greater than' from dual
union all select 3, '$', 'dollars' from dual
union all select 4, '+', 'and' from dual
), fg_Data AS (
SELECT 'amount $ must be < 1 + > 2' str FROM dual
union all
SELECT 'John is > Peter + has many $' str FROM dual
union all
SELECT 'Eliana is < mary + do not has many $' str FROM dual
), q(str, id) as (
SELECT str, 0 id
FROM fg_Data
UNION ALL
SELECT replace(q.str,symbol,text), fg_rulez.id
FROM q
JOIN fg_rulez
ON q.id = fg_rulez.id - 1
)
SELECT str from q where id = (select max(id) from fg_rulez);

If the number of values to replace is too big or you need to be able to easily maintain it, you could also split the string, use a dictionary table and finally aggregate the results
In the example below I'm assuming that the words in your string are separated with blankspaces and the wordcount in the string will not be bigger than 100 (pivot table cardinality)
with Dict as
(select '{1}' String, 'myfirstval' Repl from dual
union all
select '{2}' String, 'mysecondval' Repl from dual
union all
select '{3}' String, 'mythirdval' Repl from dual
union all
select '{Nth}' String, 'myNthval' Repl from dual
)
,MyStrings as
(select 'This is the first example {1} ' Str, 1 strnum from dual
union all
select 'In the Second example all values are shown {1} {2} {3} {Nth} ', 2 from dual
union all
select '{3} Is the value for the third', 3 from dual
union all
select '{Nth} Is the value for the Nth', 4 from dual
)
-- pivot is used to split the stings from MyStrings. We use a cartesian join for this
,pivot as (
Select Rownum Pnum
From dual
Connect By Rownum <= 100
)
-- StrtoRow is basically a cartesian join between MyStings and Pivot.
-- There as many rows as individual string elements in the Mystring Table
-- (Max = Numnber of rows Mystring table * 100).
,StrtoRow as
(
SELECT rownum rn
,ms.strnum
,REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) TXT
FROM MyStrings ms
,pivot pv
where REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) is not null
)
-- This is the main Select.
-- With the listagg function we group the string together in lines using the key strnum (group by)
-- The NVL gets the translations:
-- if there is a Repl (Replacement from the dict table) then provide it,
-- Otherwise TXT (string without translation)
Select Listagg(NVL(Repl,TXT),' ') within group (order by rn)
from
(
-- outher join between strings and the translations (not all strings have translations)
Select sr.TXT, d.Repl, sr.strnum, sr.rn
from StrtoRow sr
,dict d
where sr.TXT = d.String(+)
order by strnum, rn
) group by strnum

If you are doing this inside of a select, you can just piece it together, if your replacement values are columns, using string concatenation.

Related

Oracle ordering with IN clause [duplicate]

Is it possible to keep order from a 'IN' conditional clause?
I found this question on SO but in his example the OP have already a sorted 'IN' clause.
My case is different, 'IN' clause is in random order
Something like this :
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
I would like to retrieve results in (45,2,445,12,789) order. I'm using an Oracle database. Maybe there is an attribute in SQL I can use with the conditional clause to specify to keep order of the clause.
There will be no reliable ordering unless you use an ORDER BY clause ..
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
order by case TestResult.SomeField
when 45 then 1
when 2 then 2
when 445 then 3
...
end
You could split the query into 5 queries union all'd together though ...
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 4
union all
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 2
union all
...
I'd trust the former method more, and it would probably perform much better.
Decode function comes handy in this case instead of case expressions:
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
ORDER BY DECODE(SomeField, 45,1, 2,2, 445,3, 12,4, 789,5)
Note that value,position pairs (e.g. 445,3) are kept together for readability reasons.
Try this:
SELECT T.SomeField,T.OtherField
FROM TestResult T
JOIN
(
SELECT 1 as Id, 45 as Val FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 445 FROM dual UNION ALL
SELECT 4, 12 FROM dual UNION ALL
SELECT 5, 789 FROM dual
) I
ON T.SomeField = I.Val
ORDER BY I.Id
There is an alternative that uses string functions:
with const as (select ',45,2,445,12,789,' as vals)
select tr.*
from TestResult tr cross join const
where instr(const.vals, ','||cast(tr.somefield as varchar(255))||',') > 0
order by instr(const.vals, ','||cast(tr.somefield as varchar(255))||',')
I offer this because you might find it easier to maintain a string of values rather than an intermediate table.
I was able to do this in my application using (using SQL Server 2016)
select ItemID, iName
from Items
where ItemID in (13,11,12,1)
order by CHARINDEX(' ' + Convert("varchar",ItemID) + ' ',' 13 , 11 , 12 , 1 ')
I used a code-side regex to replace \b (word boundary) with a space. Something like...
var mylist = "13,11,12,1";
var spacedlist = replace(mylist,/\b/," ");
Importantly, because I can in my scenario, I cache the result until the next time the related items are updated, so that the query is only run at item creation/modification, rather than with each item viewing, helping to minimize any performance hit.
Pass the values in via a collection (SYS.ODCINUMBERLIST is an example of a built-in collection) and then order the rows by the collection's order:
SELECT t.SomeField,
t.OtherField
FROM TestResult t
INNER JOIN (
SELECT ROWNUM AS rn,
COLUMN_VALUE AS value
FROM TABLE(SYS.ODCINUMBERLIST(45,2,445,12,789))
) i
ON t.somefield = i.value
ORDER BY rn
Then, for the sample data:
CREATE TABLE TestResult ( somefield, otherfield ) AS
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 12, 'C' FROM DUAL UNION ALL
SELECT 37, 'D' FROM DUAL UNION ALL
SELECT 45, 'E' FROM DUAL UNION ALL
SELECT 100, 'F' FROM DUAL UNION ALL
SELECT 445, 'G' FROM DUAL UNION ALL
SELECT 789, 'H' FROM DUAL UNION ALL
SELECT 999, 'I' FROM DUAL;
The output is:
SOMEFIELD
OTHERFIELD
45
E
2
A
445
G
12
C
789
H
fiddle

Retrieve distinct values with LISTAGG in Oracle 12C [duplicate]

This question already has answers here:
LISTAGG in Oracle to return distinct values
(24 answers)
Closed 2 years ago.
I am trying to retrieve multiple concatenated distinct varchars (named CODE in query) from multiple rows on multiple columns using LISTAGG in oracle 12C, LISTAGG(distinct...) solves the problem on 19c but I must work with 12c.
Unexpected result
I get the above result using this query:
SELECT
T.c1 A,
T.c2 B,
LISTAGG( TI.CODE , ';' ) WITHIN GROUP (ORDER BY TI.CODE) AS COLUMNX1,
LISTAGG( TE.CODE, ' ;') WITHIN GROUP (ORDER BY TE.CODE ) AS COLUMNX2,
LISTAGG(TR.CODE, '; ') WITHIN GROUP (ORDER BY TR.CODE ) AS COLUMNX3
FROM TABLE1 T
INNER join TABLE_I TI on TI.fk_c2 = T.c2
INNER join TABLE_E TE on TE.fk_c2 = T.c2
INNER join TABLE_R TR on TR.fk_c2 = T.c2
WHERE T.d = *parameter*
GROUP BY
T.c1,
T.c2;
I want to retrieve this :
Expected result
The yellow marked strings should not be retrieved.
In evey line of the query result, the columns COLUMNX1, COLUMNX2, COLUMNX3 have the same number of concatenated strings, that's why I have the duplication problem.
furthermore, TABLE_I, TABLE_E and TABLE_R all have a foreign key fk_c2 that references TABLE1.c2
EDIT:
I added a with Clause to retrieve distinct values first then I joined it to my select statement
Expected result is retrieved with this query
WITH TEMP AS (
SELECT fk_c2, LISTAGG(code, ',') WITHIN GROUP (ORDER BY code) AS X1
FROM (
SELECT DISTINCT *
FROM TABLE_I
GROUP BY fk_c2 ) COLUMNX1
INNER JOIN
(SELECT fk_c2, LISTAGG(code, ',') WITHIN GROUP (ORDER BY code) AS X2
FROM (
SELECT DISTINCT *
FROM TABLE_E)
GROUP BY fk_c2 ) COLUMNX2
ON COLUMNX1.fk_c2 = COLUMNX2.fk_c2
INNER JOIN
(SELECT fk_c2, LISTAGG(code, ',') WITHIN GROUP (ORDER BY code) AS X3
FROM(
SELECT DISTINCT *
FROM TABLE_R)
GROUP BY fk_c2 ) COLUMNX3
ON COLUMNX1.fk_c2 = COLUMNX3.fk_c2
)
SELECT
T.c1 A,
T.c2 B,
tmp.X1,
tmp.X2,
tmp.X3
FROM TABLE1 T
INNER join temp tmp on tmp.fk_c2 = T.c2
WHERE T.d = *parameter*
GROUP BY
T.c1,
T.c2
tmp.X1,
tmp.X2,
tmp.X3;
You'll need additional step: first find distinct values, then aggregate them. For example:
SQL> with test (id, col) as
2 (select 1, 'x' from dual union all
3 select 1, 'x' from dual union all
4 --
5 select 2, 'w' from dual union all
6 select 2, 't' from dual union all
7 select 2, 'w' from dual union all
8 --
9 select 3, 'i' from dual
10 ),
11 -- first find distinct values ...
12 temp as
13 (select distinct id, col from test)
14 -- ... then aggregate them
15 select id,
16 listagg(col, ';') within group (order by col) result
17 from temp
18 group by id;
ID RESULT
---------- ----------
1 x
2 t;w
3 i
SQL>

Oracle Replace function using a dictionary [duplicate]

This question already has answers here:
how to replace multiple strings together in Oracle
(5 answers)
Closed 7 years ago.
I am trying to implement a simple conversion logic to clean up data on huge oracle table using nested replace function like below ,the rules are simple for now say
LKP_TABLE
-----------
LTD ----> LIMITED
COMP ----> COMPANY
SELECT REPLACE (REPLACE ( UPPER ('This is AA LTD_COMP') ,'LTD',
'LIMITED'),'COMP','COMPANY') from dual
--output : THIS IS AN AA LIMITED_COMPANY
But in future this can be a long list and I was wondering if there is any solution other than nested replace function. TRANSLATE function can replace only specific characters only.
Note : I have restrictions in creating a custom PL/SQL functions
You can maybe try with splitting the string into words, translating each word and afterwards aggregating the string once again.
In the example below, I've assumed that the words will be separated by blankspaces and that the maximum number of words is 7 (pivot table)
with replacements as
(select 'LTD' String, 'Limited' Repl from dual
union all
select 'COMP' String, 'Company' Repl from dual
)
,MyStrings as
(select 'This is AA LTD COMP' Str, 1 strnum from dual
union all
select 'This is BB LTD COMP', 2 from dual
union all
select 'This is BB COMP', 3 from dual
)
,pivot as (
select 1 pnum from dual
union all select 2 from dual
union all select 3 from dual
union all select 4 from dual
union all select 5 from dual
union all select 6 from dual
union all select 7 from dual
)
,StrtoRow as
(
SELECT rownum rn
,ms.strnum
,REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) TXT
FROM MyStrings ms
,pivot pv
where REGEXP_SUBSTR (Str,'[^ ]+',1,pv.pnum) is not null
)
Select Listagg(NVL(Repl,TXT),' ') within group (order by rn)
from
(
Select sr.TXT, r.Repl, sr.strnum, sr.rn
from StrtoRow sr
,replacements r
where sr.TXT = r.String(+)
order by strnum, rn
) group by strnum

How to compare items in an array to those in a database column using regular expressions?

I'm trying to take a list of elements in an array like this:
['GRADE', 'GRATE', 'GRAPE', /*About 1000 other entries here ...*/ ]
and match them to their occurrences in a column in an Oracle database full of entries like this:
1|'ANTERIOR'
2|'ANTEROGRADE'
3|'INGRATE'
4|'RETROGRADE'
5|'REIGN'
...|...
/*About 1,000,000 other entries here*/
For each entry in that array of G words, I'd like to loop through the word column of the Oracle database and try to find the right-sided matches for each entry in the array. In this example, entries 2, 3, and 4 in the database would all match.
In any other programming language, it would look something like this:
for entry in array:
for each in column:
if entry.right_match(each):
print entry
How do I do this in PL/SQL?
In PL/SQL it can be done in this way:
declare
SUBTYPE my_varchar2_t IS varchar2( 100 );
TYPE Roster IS TABLE OF my_varchar2_t;
names Roster := Roster( 'GRADE', 'GRATE', 'GRAPE');
begin
FOR c IN ( SELECT id, name FROM my_table )
LOOP
FOR i IN names.FIRST .. names.LAST LOOP
IF regexp_like( c.name, names( i ) ) THEN
DBMS_OUTPUT.PUT_LINE( c.id || ' ' || c.name );
END IF;
END LOOP;
END LOOP;
end;
/
but this is row by row processing, for large table it would be very slow.
I think it might be better to do it in a way shown below:
create table test123 as
select 1 id ,'ANTERIOR' name from dual union all
select 2,'ANTEROGRADE' from dual union all
select 3,'INGRATE' from dual union all
select 4,'RETROGRADE' from dual union all
select 5,'REIGN' from dual ;
create type my_table_typ is table of varchar2( 100 );
/
select *
from table( my_table_typ( 'GRADE', 'GRATE', 'GRAPE' )) x
join test123 y on regexp_like( y.name, x.column_value )
;
COLUMN_VALUE ID NAME
------------- ---------- -----------
GRADE 2 ANTEROGRADE
GRATE 3 INGRATE
GRADE 4 RETROGRADE

Invalid number of returned comma delimited string in a IN clause in Oracle

I am trying to use a subquery that returns a comma delimited string in a IN clause.
The following way:
SELECT p.person_id, g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT person_ids FROM other WHERE other_id = :OTHER_ID
)
ORDER BY lower(GROUP_PATH)
And I am getting the following error:
ORA-01722: invalid number.
Is there a better way to do this or even possible?
The most obvious explanation is that you are trying to do math with a string...
The attempted conversion of a
character string to a number failed
because the character string was not a
valid numeric literal. Only numeric
fields or character fields containing
numeric data may be used in arithmetic
functions or expressions. Only numeric
fields may be added to or subtracted
from dates.
http://ora-01722.ora-code.com/
Update #1:
Your description worries me:
I am trying to use a subquery that
returns a comma delimited string in a
IN clause.
Your subquery should not return a comma delimited string (unless g.group_id is a string and expects a comma delimited string). You must retrieve individual items in as many rows as needed (less than 1,000 anyway).
Update #2:
Just to make it clear:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO;
FOO_ID
----------------------
1
2
3
You can do this:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (1, 2);
FOO_ID
----------------------
1
2
But not this:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN ('1,2');
SQL Error: ORA-01722: invalid number
Because you cannot compare number 1 with string '1,2'. Subqueries follow similar rules:
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL
);
FOO_ID
----------------------
1
2
SELECT *
FROM (
SELECT 1 AS FOO_ID FROM DUAL UNION SELECT 2 FROM DUAL UNION SELECT 3 FROM DUAL
) FOO
WHERE FOO_ID IN (
SELECT '1,2' AS FOO_ID FROM DUAL
);
SQL Error: ORA-01722: invalid number
At a minimum, in order to reference the alias GROUP_PATH, you would need to need to use a nested subquery where the alias is defined before you reference it in your ORDER BY clause. That's realistically not causing the ORA-01722 error, but it is a problem
SELECT group_id, group_path
FROM (SELECT g.group_id,
g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT person_ids FROM other WHERE other_id = :OTHER_ID
)
ORDER BY lower(GROUP_PATH)
If the PERSON_IDS column in the OTHER table is a comma separated list of values, your IN list is not going to do what you'd expect. You would need to transform the scalar string (that happens to have commas in it) into some sort of collection of multiple PERSON_ID values. There are various approaches to doing this, Tom Kyte has one example of using a variable IN list. Assuming you copy Tom's IN_LIST function, you should be able to do something like
SELECT group_id, group_path
FROM (SELECT g.group_id,
g.name || '>>' || p.firstname || ' ' || p.lastname as GROUP_PATH
FROM PERSON p
LEFT JOIN GROUP g ON (
g.group_id = p.group_id
)
WHERE p.person_id IN (
SELECT column_value
FROM TABLE(SELECT in_list(person_ids)
FROM other
WHERE other_id = :OTHER_ID)
)
ORDER BY lower(GROUP_PATH)

Resources