SQL - Handling String - sql

for example, i have the string 'This Is An Example Of The String'
and i want to return this result 'This is an Example of the String'
==> I want all the 'Is', 'An' 'Of' and 'The' in lower cases, the rest should stay in Initcap.
How can be this done in a simple and unique query? here is my query to lower case only the 'Of' :
SELECT 'This Is An Example Of The String',
CASE
WHEN 'This Is An Example Of The String' like '% Of %'
THEN replace('This Is An Example Of The String', ' Of ', ' of ')
END
FROM dual ;
Thanks!

Try this:
SELECT REPLACE(REPLACE(REPLACE(REPLACE('This Is An Example Of The String', 'Of', 'of'), 'The', 'the'), 'An', 'an'), 'Is', 'is') FROM dual;
I feel a little dirty after writing that though.
Edit: Removed the extra 'an' replace, then added indentation, then removed indentation again. It looks ugly no matter how you wrap it.

Basically it's going to take a lot of logic of the nature you've described. There's no quick and easy way. You'd find it quicker and easier to do this type of manipulation in your business logic code rather than at the database.
If you are going to do it in the database consider wrapping up the logic in a function, like this one.

Although it's not a pure SQL solution, another option would be to define a function which transformed the string as desired, perhaps calling it REPLACE_MULTI. The invocation would be something like
SELECT REPLACE_MULTI('This Is An Example Of The String',
'Is|An|Of|The',
'is|an|of|the')
FROM DUAL;
and the implementation would be something along the lines of
CREATE OR REPLACE FUNCTION REPLACE_MULTI(strOriginal IN VARCHAR2,
strTokens_to_replace IN VARCHAR2,
strReplacement_tokens IN VARCHAR2)
RETURN VARCHAR2
IS
strResult VARCHAR2(2000);
arrTokens_to_replace DBMS_SQL.VARCHAR2A;
arrReplacement_tokens DBMS_SQL.VARCHAR2A;
i NUMBER;
FUNCTION extract_tokens(p_string IN VARCHAR2,
p_separators IN VARCHAR2) RETURN DBMS_SQL.VARCHAR2A
IS
arrTokens DBMS_SQL.VARCHAR2A;
BEGIN
WITH sel_string AS
(SELECT p_string AS fullstring FROM DUAL)
SELECT SUBSTR(fullstring, beg + 1, end_p - beg - 1) AS token
BULK COLLECT INTO arrTokens
FROM (SELECT beg, LEAD(beg) OVER (ORDER BY beg) AS end_p, fullstring
FROM (SELECT beg, fullstring
FROM (SELECT LEVEL beg, fullstring
FROM sel_string
CONNECT BY LEVEL <= LENGTH(fullstring))
WHERE INSTR(p_separators, SUBSTR(fullstring, beg, 1)) > 0
UNION ALL
SELECT 0, fullstring FROM sel_string
UNION ALL
SELECT LENGTH(fullstring) + 1, fullstring FROM sel_string))
WHERE end_p IS NOT NULL AND
end_p > beg + 1;
RETURN arrTokens;
END extract_tokens;
BEGIN
arrTokens_to_replace := extract_tokens(strTokens_to_replace, '|');
arrReplacement_tokens := extract_tokens(strReplacement_tokens, '|');
strResult := strOriginal;
FOR i IN 1..arrTokens_to_replace.COUNT LOOP
strResult := REGEXP_REPLACE(strResult,
'^' || arrTokens_to_replace(i) || ' ',
arrReplacement_tokens(i));
strResult := REPLACE(strResult,
' ' || arrTokens_to_replace(i) || ' ',
' ' || arrTokens_to_replace(i) || ' ');
strResult := REGEXP_REPLACE(strResult,
' ' || arrTokens_to_replace(i) || '$',
' ' || arrReplacement_tokens(i));
END LOOP;
RETURN strResult;
END REPLACE_MULTI;
I'm sure that there are token strings which can be created which will break the regular expression-based parsing (try putting a '^' or '$' in there and watch the sparks fly :-) but this is good enough for an initial hack.
(Incidentally, the 'extract_tokens' routine isn't mine - I found it on the web somewhere a while back and am eternally grateful to whoever it was that created this).
Share and enjoy.

I can tell you the algorithm but I am not sure how to do that in SQL. for example:
words[] = string.split(" ")
foreach word in words
---if(word.length<=3) //do that only for short words
-------if(word[i]>=65 && word[i]<=90) //check the ascii code for upper case
------------word[i] += 21; // transfer it into lower case
---new sentence += " " + words[i] //add to the resultant string

Related

Snowflake : REGEXP replace with uppercase of capture group

I want to replace the very first letter after a comma(,) with uppercase of it in snowflake database. Below given is what I tried, but it did not work.
eg:
Apple,ball,cat --> Apple,Ball,Cat
Bulb,LED,tube --> Bulb,LED,Tube
SELECT REGEXP_REPLACE('Apple,ball,cat',',(\\\w)',UPPER('\\\1'));
,(\\\w) captures letters after the comma, but UPPER('\\\1') does not convert it to uppercase.
I am not sure if you can use functions inside REGEXP_REPLACE at all.
Please use the built-in INITCAP function
SELECT INITCAP('Apple,ball,cat', ',');
Reference: INITCAP
Or maybe like this:
SELECT LISTAGG(UPPER(LEFT(VALUE, 1)) || SUBSTRING(VALUE, 2, LEN(VALUE)), ',')
FROM TABLE(SPLIT_TO_TABLE('Apple,ball,cat', ',')) as t(val);
Not "regex", but if you're interested in a Javascript UDF to do what you need...
CREATE OR REPLACE FUNCTION fx_replaceInitOnly(
input varchar)
returns varchar
language javascript
as '
//logic from https://www.freecodecamp.org/news/how-to-capitalize-words-in-javascript/
var words = INPUT.split(",");
for (let i = 0; i < words.length; i++) {
words[i] = words[i][0].toUpperCase() + words[i].substr(1);
}
output = words.join(",");
return output;
';
SELECT
'Apple,ball,cat,Bulb,LED,Tube' as str,
fx_replaceInitOnly(str) as new,
case WHEN str <> new THEN 'Changed' ELSE 'Same' END as test;
--STR NEW TEST
--Apple,ball,cat,Bulb,LED,Tube Apple,Ball,Cat,Bulb,LED,Tube Changed
Regexp will not help you to upper your chars, so you may combine split_to_table and initcap:
SELECT LISTAGG( INITCAP(VALUE) ,',' )
FROM TABLE(SPLIT_TO_TABLE('Apple,ball,cat',','));

Oracle replace a word in the middle

fse.name = ASD-122-XXX and fn.name = ASD-125-FFF
I want to put all fn.name the 122 of the fse.name associate to the fse.name
I have this but it doesn't work the way I want
var_rama VARCHAR2;
Begin
FOR var in (select fse.name ,fn.name as nombreFnode, fn.descr
from fiberspliceenclosure_lt fse
inner join segments seg
on fse.mslink = seg.link1
inner join fibernode fn
on fn.mslink = seg.link2
where fse.name like 'CO905%'
and fn.descr like '04-%')
LOOP
var.(substr(fse.name,7,5)) := var_rama;
replace (substr (fn.name,7,5), var_rama);
END LOOP;
If your names are always in the same format i.e. AAA-999-AAA this is fairly simple with just substr() calls:
select substr(fn.name, 1, 4)
|| substr(fse.name, 5, 3)
|| substr(fn.name, 8) as new_name
from fn
join fse on fn.id = fse.id
/
However, if the name formats can vary - say AAAA-99-AAAAA is valid - then we can't use fixed offsets. So the code becomes more complicated, with instr() calls to identify the position of each dash:
select substr(fn.name, 1, instr(fn.name, '-', 1, 1))
|| substr(fse.name, instr(fse.name, '-', 1, 1)+1, instr(fse.name, '-', 1, 2) - instr(fse.name, '-', 1, 1)-1 )
|| substr(fn.name, instr(fn.name, '-', 1, 2)) as new_name
from fn
join fse on fn.id = fse.id
/
No doubt there is a snazzier solution using regular expressions but I prefer to avoid regex for tasks which can be done with substr() and instr() for performance reasons.
Why dont you try with INSTR function to find out whether your finding string is there in you destination variable or not? if its there then just use REPLACE to replace the string with your desire string. I guess, this would work for you..because every time you dont need to find the position of your search string.
As per my understanding, he want to replace 122 from fse.name with fn.name string. so below is my code for that.
Declare
v_instr number;
v_out varchar2(50);
begin
for i in (sql_query) loop
v_instr := instr(i.fse_name,'122');
if v_instr >0 then
v_out := replace(i.fse_name,'122',i.fn_name);
end if;
end loop;
end;

regexp_substr for getting correct result

I was trying to use regexp_substr to get each correct column name from a column list string.
The query is like:
select regexp_substr(v_keep, '(^|[(,) ]*)' || r.column_name || '($|[(,) ]+)', 1, 1, 'i')
from dual;
But the result is not correct.
The v_keep can be any column name list like abc, abc_abc, abc1 or (abc, abc_abc, abc1).
The r.column_name can be like abc or ab.
- If the input v_keep is (abc, abc_abc, abc1) and the r.column_name is
ab, it will return null.
- If the input v_keep is (abc, abc_abc, abc1) and the r.column_name is
abc, it will return the column name just abc.
Can anyone help me to fix it by just modify the pattern inside the regexp_substr ?
Why not just use a case and like?
select (case when replace(replace(v_keep, '(', ','), '(', ',')) like '%,' || r.column_name || ',%'
then r.column_name
end)
I don't recommend storing lists in a comma-delimited string, but if you are, this is one way to identify individual elements of the list.
It's pretty simple, you just need to add a subexpression so you can pull out the part of the string you want. (A subexpression is a section of the regexp in parentheses.) In this case the last argument is 2, because you want the part of the match that corresponds to the second group of parentheses.
regexp_substr(v_keep, '(^|[(,) ]*)(' || r.column_name || ')($|[(,) ]+)', 1, 1, 'i', 2)
Gordon's solution will have better performance, though.
Edit: working example -
with testdata as (select '(abc, abc_abc, abc1)' as v_keep, 'abc' as column_name from dual)
select regexp_substr(v_keep, '(^|[(,) ]*)(' || r.column_name || ')($|[(,) ]+)', 1, 1, 'i', 2)
from testdata r;
Since this is PL/SQL code to see if a value is in a string, try this which avoids the overhead of hitting the database, and calling REGEXP. Just keep it straight SQL. I hate the nested REPLACE calls but I was trying to avoid using REGEXP_REPLACE although it could be done in one call if you did use it.
set serveroutput on;
set feedback off;
declare
v_keep varchar2(50) := '(abc, abc_abc, abc1)';
compare varchar2(10) := 'abc_';
begin
if instr(',' || replace(replace(replace(v_keep, ' '), '('), ')') || ',', ',' || compare || ',') > 0 then
dbms_output.put_line('Column ''' || compare ||''' IS in the keep list');
else
dbms_output.put_line('Column ''' || compare ||''' IS NOT in the keep list');
end if;
end;

Selecting individual values from csv format in oracle pl sql

I have the following value in a column in Oracle db ('abc', 'xyz')
I want to extract the values separately like abc, xyz by removing ' and (). Is there a way to do it using INSTR and SUBSTR functions?
Thanks
Use this query:
with sample as (select '(''abc'', ''xyz'')' text from dual)
select substr(text,instr(text,'''',1,1) + 1,instr(text,'''',1,2) - instr(text,'''',1,1) - 1),
substr(text,instr(text,'''',1,3) + 1,instr(text,'''',1,4) - instr(text,'''',1,3) - 1)
from sample;
It would help to know what you want to do with the data once parsed. How it could be handled in SQL vs PL/SQL to achieve your requirement could be very different.
That said, here's one way to strip surrounding parens and remove single quotes at the same time during the select using the powerful regexp_replace(source_string, pattern_string, replace_string) :
WITH qry AS (SELECT '(' || '''abc''' || ',' || '''xyz''' || ')' orig_string
FROM dual
)
SELECT regexp_replace(orig_string, '[()'']', '' ) clean_string
FROM qry;
The regexp_replace pattern_string says to match a character class (defind by opening and closing square brackets) containing a left paren or a right paren or a single quote (quoted so Oracle sees it) and the replace_string replaces it with nothing.
Then, to parse the values remaining here's an example from by bag of tricks I got somewhere and tweaked for this case:
set serveroutput on
DECLARE
-- Build a string in the format "('abc','xyz')"
orig_string varchar2(20) := '(' || '''abc''' || ',' || '''xyz''' || ')';
CURSOR cur IS
WITH qry AS (SELECT regexp_replace(orig_string, '[()'']','' ) clean_string
FROM dual
)
SELECT regexp_substr(clean_string, '[^,]+', 1, ROWNUM) element
FROM qry
CONNECT BY LEVEL <= LENGTH(regexp_replace (clean_string, '[^,]+')) + 1;
BEGIN
FOR rec IN cur LOOP
dbms_output.put_line('Element:' || rec.element);
END LOOP;
END;
It basically loops through the elements and prints them. I'm sure you can adapt this to your situation.

How to reverse a string after tokenizing it in SQL

I need to tokenize a string and reverse it in SQL. For example if the string is, 'L3:L2:L1:L0', i need to reverse it as 'L0:L1:L2:L3'. The tokenizing could be done using a delimiter ':' and then reverse it. Please suggest a Function in SQL for the same.
Thanks in advance,
Geetha
If possible, the best solution would be to change your data so that each value is stored in a different row.
If that doesn't work, you can create a PL/SQL function.
If you want a purely SQL solution, typically you'll have to split each value into multiple rows (cross join with an object table, or connect by level <= max number of items), and then re-aggregate the data using one of a dozen different methods (listagg, collect, stragg, xml, sys_connect_by_path, etc.)
Another SQL-only way is to use regular expressions. This is probably the fastest, but it only works with up to 9 items because Oracle only supports 9 back references:
--Get everything except the extra ':' at the end.
select substr(string, 1, length(string) - 1) string from
(
select regexp_replace(
--Add a delimter to the end so all items are the same
'L3:L2:L1:L0'||':'
--Non-greedy search for anything up to a : (I bet there's a better way to do this)
,'(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?(.*?:)?'
--Reverse the back-references
,'\9\8\7\6\5\4\3\2\1') string
from dual
);
Something like :
SELECT
REGEXP_REPLACE('L1:L2:L3',
'([[:alnum:]]{1,}):([[:alnum:]]{1,}):([[:alnum:]]{1,})',
'\3 \2 \1') "REGEXP_REPLACE"
from dual
But you might need to detail what constitutes a token.
Here is a solution using a PL/SQL pipelined function to split the elements:
create type t_str_array as table of varchar2(4000);
create or replace function split_str (p_str in varchar2,
p_separator in varchar2 := ':') return t_str_array pipelined
as
l_str varchar2(32000) := p_str || p_separator;
l_pos pls_integer;
begin
loop
l_pos := instr(l_str, p_separator);
exit when (nvl(l_pos,0) = 0);
pipe row (ltrim(rtrim(substr(l_str,1,l_pos-1))));
l_str := substr(l_str, l_pos+1);
end loop;
return;
end split_str;
Then you would use normal SQL to order the elements:
select * from table(split_str('L3:L2:L1:L0')) order by column_value
declare
s varchar2(1000) := 'L 1 0:L9:L8:L7:L6:L5:L4:L3:L2:L1:L0';
j number := length(s);
begin
for i in reverse 1..length(s) loop
if substr(s, i, 1) = ':' then
dbms_output.put(substr(s, i + 1, j - i) || ':');
j := i - 1;
end if;
end loop;
dbms_output.put_line(substr(s, 1, j));
end;
Convert elements in a CSV string into records, suppressing all NULLs:
SELECT REGEXP_SUBSTR( :csv,'[^,]+', 1, LEVEL ) AS element
FROM dual
CONNECT BY REGEXP_SUBSTR( :csv, '[^,]+', 1, LEVEL ) IS NOT NULL ;
Convert elements in a CSV string into records, preserving NULLs (but not order):
SELECT REGEXP_SUBSTR( :csv,'[^,]+', 1, LEVEL ) AS element
FROM dual
CONNECT BY LEVEL <= LENGTH( :csv ) - LENGTH( REPLACE( :CSV, ',' ) ) + 1 ;
Improving upon Kevan's answer, here is what I tried:
select listagg(TOKEN, ':') WITHIN GROUP (ORDER BY TOKEN_LEVEL DESC)
from
(SELECT REGEXP_SUBSTR( myStr,'[^:]+', 1, LEVEL ) AS TOKEN, LEVEL TOKEN_LEVEL
FROM dual
CONNECT BY REGEXP_SUBSTR( myStr, '[^:]+', 1, LEVEL ) IS NOT NULL);
Since you use Oracle it would be easy to generate a java stored procedure passing the string and then
split sting into array
loop array backwards and concate the resulting string
return the resulting string
this will be a small java code and not slower then pl/sql. but if you want to use pl/sql you can possibly also use DBMS_UTILITY.table_to_comma/.comma_to_table. But as the function name let assume -> you have to use "," as token.