REGEXP_REPLACE how to using multiple replacement_string - regex

how to multiple replacement string using regexp_replace in oracle?
formula = 1*3
formula detail (1->value1, 2->value3, 3->value3)
I want the result
formula = value1*value3

I don't understand what this:
formula = 1*3
formula detail (1->value1, 2->value3, 3->value3)
actually represents. What is the first row? Is it a string, stored in some table? Is it a string stored in a variable? Is it the complete string, or is the string just 1*3 (without formula =)?
What is the second row? The same doubts as for the first one.
Anyway: if we pretend that the first row represents a string, while the second represents your wish and not some code, then nested REPLACE (i.e. no regular expressions at all) does the job:
SQL> create or replace function f_rep
2 (par_1 in varchar2, par_2 in varchar2, par_3 in varchar2)
3 return varchar2
4 is
5 l_str varchar2(200) := 'formula = 1*3';
6 begin
7 l_str := replace(replace(replace(l_str, '1', par_1),
8 '2', par_2),
9 '3', par_3);
10 return l_str;
11 end;
12 /
Function created.
SQL> select f_rep('value1', null, 'value3') result from dual;
RESULT
--------------------------------------------------------------------------------
formula = value1*value3
SQL>

Related

Oracle PLSQL regexp_substr separate comma separated string with double quotes

I've seen examples of how to separate comma-separated strings into rows like this:
select distinct id, trim(regexp_substr(value,'[^,]+', 1, level) ) value, level
from tbl1
connect by regexp_substr(value, '[^,]+', 1, level) is not null
order by id, level;
but, my question is, how do I do this on double quote and comma delimited strings?
Ex: the above works for strings like "1,2,3,4,5,6,7", but what about "1","2","3","4,5","6,7,8","9" so that the rows end up like:
1
2
3
4,5
6,7,8
9
edit: I'm on Oracle 11.2.0.4, 11gR2.
There is a hack. Replace the pattern "," with # and use it in regular expression.It works like a charm.
Input String : "1","2","3","4,5","6,7,8","9","Ant,B","Gradle","E,F","G"(Can be number/Character doesn't matter)
with temp as (
select replace(replace('"1","2","3","4,5","6,7,8","9","Ant,B","Gradle","E,F","G"','","','#'),'"') Colmn from dual
)
SELECT trim(regexp_substr(str, '[^#]+', 1, level)) str
FROM (SELECT Colmn str FROM temp) t
CONNECT BY instr(str, '#', 1, level - 1) > 0
Output :
STR
1
2
3
4,5
6,7,8
9
Ant,B
Gradle
E,F
G
10 rows
Refer DBFiddle link for demo.
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=d09c326f614d10f5d3c407fdfd3a44c5
here is the another solution.it must be working all string with have number.
i used number values index as base.
with temp as (
select '"1","2","3",x"4,5","6,73,8","9"' Colmn from dual
)
SELECT regexp_substr(Colmn, '\d{1}', REGEXP_INSTR(Colmn, '\d{1}', REGEXP_INSTR(Colmn, '\d{1}') ,level),1 ) from temp
CONNECT BY REGEXP_COUNT (Colmn,'\d{1}')+1> level

get integers from string

i have in the database data like this
61/10#61/12,0/12,10/16,0/21,0/12#61/33,0/28#0/34,0/23#0/28
where the part like 10/16(without #) is invalid should not use for the calculation,
but all other has next format min_hr + "/" + min_hrv + "#" + max_hr + "/" + max_hrv
and the issue is get AVG value by next psevdo formula [ summ(all(min_hrv)) + summ(all(max_hrv)) ] / count(all(min_hrv)) + all(max_hrv)), for the axample string result will be ((10 + 12 + 28 + 23) + (12 + 33 + 34 + 28))/8) == 22
What i try is:
SELECT regexp_replace(
'61/10#61/12,0/12,10/16,0/21,0/12#61/33,0/28#0/34,0/23#0/28',
',\d+/\d+,', ',',
'g'
);
to remove invalid data but 10/16 still in the strin, result is:
regexp_replace
--------------------------------------------------
61/10#61/12,10/16,0/12#61/33,0/28#0/34,0/23#0/28
if do good clean the string my plan is split to array some way like this, for max (not full solution, has empty string), has no solution for min:
SELECT
regexp_split_to_array(
regexp_replace(
'61/10#61/12,0/12,0/12#61/33,0/28#0/34,0/23#0/28',
',\d+/\d+,', ',',
'g'
)
,',?\d+/\d+#\d+/'
);
result is:
regexp_split_to_array
-----------------------
{"",12,33,34,28}
and then calculate the data, something like this:
SELECT ((
SELECT sum(tmin.unnest)
FROM
(SELECT unnest('{10,12,28,23}'::int[])) as tmin
)
+
(
SELECT sum(tmax.unnest)
FROM
(SELECT unnest('{12,33,34,28}'::int[])) as tmax
))
/
(SELECT array_length('{12,33,34,28}'::int[], 1) * 2)
may be some one know more simple and right way for such issue?
Use regexp_matches():
select (regexp_matches(
'61/10#61/12,0/12,0/12#61/33,0/28#0/34,0/23#0/28',
'\d+#\d+/(\d+)',
'g'))[1]
regexp_matches
----------------
12
33
34
28
(4 rows)
The whole calculation may look like this:
with my_data(str) as (
values
('61/10#61/12,0/12,10/16,0/21,0/12#61/33,0/28#0/34,0/23#0/28')
),
min_max as (
select
(regexp_matches(str, '(\d+)#\d+', 'g'))[1] as min_hrv,
(regexp_matches(str, '\d+#\d+/(\d+)', 'g'))[1] as max_hrv
from my_data
)
select avg(min_hrv::int+ max_hrv::int) / 2 as result
from min_max;
result
---------------------
22.5000000000000000
(1 row)
The pattern you are looking for should match the digits after #, a streak of digits and a / char. With regexp_matches, you may extract a part of the pattern only if you wrap that part within a pair of parentheses.
The solution is
regexp_matches(your_col, '#\d+/(\d+)', 'g')
Note that g stands for global, meaning that all occurrences found in the string will be returned.
Pattern details
\d+ - 1 or more (+) digits
/ - a /char
(\d+) - Capturing group 1: 1 or more digits
See the regex demo.
You may extract specific bits from your data if you use a single pair of parentheses in different parts of the '(\d+)/(\d+)#(\d+)/(\d+)' regex. To extract min_hr, you'd use '(\d+)/\d+#\d+/\d+'.

PostgreSQL return an Array or Record as a Row

I'm trying to return a variable with a PostgreSQL function that returns row/rows so I can use libpqxx on the client side to iterate over it for example using:
for (pqxx::result::const_iterator row = result.begin(); row != result.end(); row++)
{
for (pqxx::const_row_iterator field = row.begin(); field != row.end(); field++)
{
cout << field << '\n';
}
}
This is my PostgresSQL function:
CREATE OR REPLACE FUNCTION seal_diff_benchmark_pgsql(sealparams CHARACTER VARYING) RETURNS RECORD AS $outputVar$
DECLARE
tempVar1 CHARACTER VARYING;
tempVar2 CHARACTER VARYING;
outputVar1 TEXT[];
outputVar record;
sealArray TEXT[];
execTime NUMERIC[];
BEGIN
FOR i IN 1..2 LOOP
SELECT "Pickup_longitude", "Dropoff_longitude" INTO tempVar1, tempVar2 FROM public.nyc2015_09_enc WHERE id=i;
sealArray := (SELECT public.seal_diff_benchmark(tempVar1, tempVar2, sealparams));
outputVar1[i] := sealArray[1];
execTime[i] := sealArray[2];
END LOOP;
SELECT UNNEST(outputVar1) INTO outputVAR;
RETURN outputVar;
END;
$outputVar$ LANGUAGE plpgsql;
I also tried returning outputVar1 as TEXT[]. My field variable on the client side holds {foo, bar} if I use returns TEXT[] or (foo) if I use returns RECORD. But this is not what I need, which is a row like return from a TEXT[] array or a RECORD variable without any (), [], {} chars at the beginning and at the end of the output.
How can I change my PostgreSQL function to make it work? I think I'm missing something but I can't see what.
There are many approaches to do what you want.
If it really is just one column that you want, then you can simply do:
CREATE OR REPLACE FUNCTION seal_diff_benchmark_pgsql(sealparams CHARACTER VARYING)
RETURNS SETOF TEXT AS $outputVar$
DECLARE
tempVar1 CHARACTER VARYING;
tempVar2 CHARACTER VARYING;
sealArray TEXT[];
execTime NUMERIC[];
outputVar text;
BEGIN
FOR i IN 1..2 LOOP
SELECT "Pickup_longitude", "Dropoff_longitude" INTO tempVar1, tempVar2
FROM public.nyc2015_09_enc WHERE id=i;
sealArray := (SELECT public.seal_diff_benchmark(tempVar1, tempVar2, sealparams));
execTime[i] := sealArray[2];
FOREACH outputVar IN ARRAY sealArray[1] LOOP --iterate over that text array
RETURN NEXT outputVar;
END LOOP;
END LOOP;
END;
$outputVar$ LANGUAGE plpgsql;
Returned colum will be named just like the function.
SELECT seal_diff_benchmark_pgsql FROM seal_diff_benchmark_pgsql('stuff');
-- alternative
SELECT seal_diff_benchmark_pgsql('stuff');
You can also specify columns in function parameters:
CREATE OR REPLACE FUNCTION seal_diff_benchmark_pgsql(sealparams CHARACTER VARYING, OUT outputVar text)
Then returned column will be named outputVar. In case of returning just one column, Postgres forces RETURNS to be of that column type, so in this case SETOF TEXT or just TEXT if one row is expected. If you return more than one column, then you need to use RETURNS SETOF RECORD.
When you use named columns in function parameters, then you need to assign values to them just like you would to variables from DECLARE section:
LOOP
outputVar := 'some value';
outputVar2 := 'some value';
outputVar3 := 'some value';
RETURN NEXT;
END LOOP;
There are a few other examples on how to return sets from functions in my old answer here: How to return rows of query result in PostgreSQL's function?

PL SQL regular expression substring

I have a long string.
message := 'I loooove my pet animal';
This string in 23 chars long. If message is greater that 15 chars, I need to find the length of message where I can break the string into 2 strings. For example, in this case,
message1 := 'I loove my'
message2 := 'pet animal'
Essentially it should find the position of a whole word at the previous to 15 chars and the break the original string into 2 at that point.
Please give me ideas how I can do this.
Thank you.
Here is a general solution - with possibly more than one input string, and with inputs of any length. The only assumption is that no single word may be more than 15 characters, and that everything between two spaces is considered a word. If a "word" can be more than 15 characters, the solution can be adapted, but the requirement itself would need to state what the desired result is in such a case.
I make up two input strings in a CTE (at the top) - that is not part of the solution, it is just for testing and illustration. I also wrote this in plain SQL - there is no need for PL/SQL code for this type of problem. Set processing (instead of one row at a time) should result in much better execution.
The approach is to identify the location of all spaces (I append and prepend a space to each string, too, so I won't have to deal with exceptions for the first and last substring); then I decide, in a recursive subquery, where each "maximal" substring should begin and where it should end; and then outputting the substrings is trivial. I used a recursive query, that should work in Oracle 11.1 (or 11.2 with the syntax I used, with column names in CTE declarations - it can be changed easily to work in 11.1). In Oracle 12, it would be easier to rewrite the same idea using MATCH_RECOGINZE.
with
inputs ( id, str ) as (
select 101, 'I loooove my pet animal' from dual union all
select 102, '1992 was a great year for - make something up here as needed' from dual
),
positions ( id, pos ) as (
select id, instr(' ' || str || ' ', ' ', 1, level)
from inputs
connect by level <= length(str) - length(replace(str, ' ')) + 2
and prior id = id
and prior sys_guid() is not null
),
r ( id, str, line_number, pos_from, pos_to ) as (
select id, ' ' || str || ' ', 0, null, 1
from inputs
union all
select r.id, r.str, r.line_number + 1, r.pos_to,
( select max(pos)
from positions
where id = r.id and pos - r.pos_to between 1 and 16
)
from r
where pos_to is not null
)
select id, line_number, substr(str, pos_from + 1, pos_to - pos_from - 1) as line_text
from r
where line_number > 0 and pos_to is not null
order by id, line_number
;
Output:
ID LINE_NUMBER LINE_TEXT
---- ----------- ---------------
101 1 I loooove my
101 2 pet animal
102 1 1992 was a
102 2 great year for
102 3 - make
102 4 something up
102 5 here as needed
7 rows selected.
First you reverse string.
SELECT REVERSE(strField) FROM DUAL;
Then you calculate length i = length(strField).
Then find the first space after the middle
j := INSTR( REVERSE(strField), ' ', i / 2, i)`
Finally split by i - j (maybe +/- 1 need to test it)
DEMO
WITH parameter (id, strField) as (
select 101, 'I loooove my pet animal' from dual union all
select 102, '1992 was a great year for - make something up here as needed' from dual union all
select 103, 'You are Supercalifragilisticexpialidocious' from dual
), prepare (id, rev, len, middle) as (
SELECT id, reverse(strField), length(strField), length(strField) / 2
FROM parameter
)
SELECT p.*, l.*,
SUBSTR(strField, 1, len - INSTR(rev, ' ', middle)) as first,
SUBSTR(strField, len - INSTR(rev, ' ', middle) + 2, len) as second
FROM parameter p
JOIN prepare l
ON p.id = l.id
OUTPUT

Get Index of number in reverse direction of a string - PLSQL

How to find out the index of the first number encountered in the reverse direction of a string?
For example: 'CUSTOMC23VBA' and 'CUSTOMC245BA'.
So, function should return as '2' or '3' from reverse or the index value as '9' or '10'.
I could get the value by hard-coding the SUBSTR('CUSTOMC23VBA', -3) but I would want it to be generic as regular expressions.
You can try:
select regexp_instr(reverse('CUSTOM123XYZ'), '[[:digit:]]',1,1) from dual
Output: 4
Zero based index would be:
select regexp_instr(reverse('CUSTOM123XYZ'), '[[:digit:]]',1,1)-1 from dual
Output: 3
If you want the rest of the string from the last number, you can use substr and take advantage of the negative position to count from end of string:
select substr('CUSTOM123XYZ', -1 * (regexp_instr(reverse('CUSTOM123XYZ'), '[[:digit:]]',1,1)-1)) from dual;
Output: XYZ
An example testing multiple input strings:
with d as (
select 'CUSTOM123XYZ' as input_str from dual
union
select 'CUSTOM123XZ' as input_str from dual
union
select 'CUSTOM 1 X 3YZ' as input_str from dual
)
select input_str,
substr(input_str, -1 * (regexp_instr(reverse(input_str), '[[:digit:]]',1,1)-1)) as result
from d
Output:
INPUT_STR RESULT
CUSTOM 1 X 3YZ YZ
CUSTOM123XYZ XYZ
CUSTOM123XZ XZ
Is there only one number in the string? if so, you could go for something like this:
select REGEXP_REPLACE('CUSTOMC23VBA', '[[:alpha:]]','') from dual
But this will fail when there are multiple numbers in the string.