Oracle replace a word in the middle - sql

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;

Related

Printing the position of the bigger letter in a list of characters

I am having some logic thinking trouble with this task.
So the task asks to return the position of the first bigger letter in a list of letters.
For example:
ABVD -> 3
BCDG -> 4
CFDE -> 2
This tasks suggests to use lenght, ascii, and named block, function
So this is what I could do so far:
declare
x varchar2(10) :='ABFD';
BEGIN
FOR i in 1..length(x) LOOP
dbms_output.put_line(ASCII(SUBSTR(x, i, 1)));
END LOOP;
END;
My thought was to turn the letters to numbers : 65, 66, 70, 68. The pattern is x + 1 and since the number 70 is not equal 66 + 1, so the program will return the position of that number, which is 3.
Unfortunately I don't know how turn this idea into code. Can you give me some hints/suggestions? Thanks!
In the problem statement you said "... use named block, function."
Your solution is an anonymous procedure. It is not named anywhere (which is why it is called "anonymous"). And it is not a function - it doesn't return anything.
I will let you study the documentation to understand the difference between function and procedure, and how to name a function or procedure. Below I will follow your lead and show how you can modify your code to make it into a workable anonymous procedure. (In the procedure I "print" the final value of ind; when you change this to a function, you should return that value, instead of printing it.)
In the code you posted, you are printing the letters in the input string, one by one. You are not even attempting to define or assign to an integer (the index of the first occurrence of the "highest" letter in the string). That should be done in the DECLARE block. Then we also need to store the highest letter found "so far" (for future comparisons).
The code might look like this:
declare
x varchar2(10) :='ABFD';
ind number := 1;
max_letter char(1) := substr(x, 1, 1);
BEGIN
FOR i in 2..length(x) LOOP
if substr(x, i, 1) > max_letter
then max_letter := substr(x, i, 1);
ind := i;
end if;
END LOOP;
dbms_output.put_line(ind);
END;
/
Note that letters can be compared to each other directly, there is no reason to convert them to numbers.
Pure SQL using model clause
with t(str) as
(select 'ABVD' from dual
union all select 'BCDG' from dual
union all select 'CFDE' from dual)
select str, instr(str, max_chr) ind
from t
model
partition by (rownum rn)
dimension by (1 dummy)
measures (str, chr(1) max_chr)
rules
iterate (4e3) until (substr(str[1], iteration_number + 2, 1) is null)
(max_chr[1] = greatest(max_chr[1], substr(str[1], iteration_number + 1, 1)));
STR IND
---- ----------
ABVD 3
BCDG 4
CFDE 2

Display count of characters repeated in a string using SQL only

I want to achieve below code snippet o/p using select query alone. Is it possible without using regexp?
Character Count when string input is dynamic.
DECLARE
str VARCHAR2(255);
lv_val NUMBER;
lv_char CHAR(1);
lv_unq VARCHAR2(255);
BEGIN
str:= :p_string;
FOR i IN 1..length(str)
LOOP
lv_val := 0;
lv_char := SUBSTR(str,i,1);
IF instr(lv_unq,lv_char)>0 THEN
NULL;
ELSE
lv_unq := lv_unq||lv_char;
lv_val := ((LENGTH(str) - LENGTH(REPLACE(replace(str,' ',''), lv_char, ''))) / LENGTH(lv_char));
--select ((length(str) - LENgth(REPLACE(str, lv_char, ''))) / LENgth(lv_char)) into lv_val FROM dual;
DBMS_OUTPUT.PUT_LINE('Character '||lv_char || ' is repeated :'||lv_val||' times in the string '||str);
END IF;
END LOOP;
END;
Answering to the question's title:
Display count of characters repeated in a string using SQL only
with v as (select substr('hello world', level, 1) c from dual connect by level < 12),
d as (select chr(ascii('a')+level-1) c from dual connect by level <= 26)
select d.c, count(v.c) from d left join v on d.c = v.c
group by d.c
order by d.c;
See http://sqlfiddle.com/#!4/d41d8/38321/0 for the result
The first view split your string into characters. The second view is just the alphabet. Once you have both views, you only need a simple left join with a group by clause to count the number of matching occurrences.
Please note:
in the first view, the string and its length are hard-coded in this example
I assume all your characters are lower-case
I only take into account the 26 (lower case) letters of the ASCII encoding.

Substring Function

The output from a column is like this 'Arpit-Bansal-Doctor-ALC FU JP-111DC'.I want to extract the data which is between the - - that is for example Bansal.
Please advice which function to use and how to use.
You can do it using the built-in functions INSTR and SUBSTR like this:
select substr(col,instr(col,'-')+1,instr(col,'-',1,2)-instr(col,'-')-1)
from (select 'Arpit-Bansal-Doctor-ALC FU JP-111DC' col from dual);
The INSTR function calls are used to find the first and second hyphen, and then SUBSTR is used to get the string between them.
This is quite cumbersome, so if you want all 5 values separated out and you are in PL/SQL you can use apex_util.string_to_table to separate them:
declare
array apex_application_global.vc_arr2;
begin
array := apex_util.string_to_table ('Arpit-Bansal-Doctor-ALC FU JP-111DC', '-');
for i in 1..array.count loop
dbms_output.put_Line ('Part' || i || ' is '||array(i));
end loop;
end;
Another method:
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+')
from dual;
Arpit
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+', 1, 2)
from dual;
Bansal
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+', 1, 3)
from dual;
Doctor
select regexp_substr('Arpit-Bansal-Doctor-ALC FU JP-111DC', '[^-]+', 1, 4)
from dual;
ALC FU JP

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.

Parameters in query with in clause?

I want to use parameter for query like this :
SELECT * FROM MATABLE
WHERE MT_ID IN (368134, 181956)
so I think about this
SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM)
but it doesn't work...
Is there a way to do this ?
I actually use IBX and Firebird 2.1
I don't know how many parameters in IN clause.
For whom ever is still interested. I did it in Firebird 2.5 using another stored procedure inspired by this post.
How to split comma separated string inside stored procedure?
CREATE OR ALTER PROCEDURE SPLIT_STRING (
ainput varchar(8192))
RETURNS (
result varchar(255))
AS
DECLARE variable lastpos integer;
DECLARE variable nextpos integer;
DECLARE variable tempstr varchar(8192);
BEGIN
AINPUT = :AINPUT || ',';
LASTPOS = 1;
NEXTPOS = position(',', :AINPUT, LASTPOS);
WHILE (:NEXTPOS > 1) do
BEGIN
TEMPSTR = substring(:AINPUT from :LASTPOS for :NEXTPOS - :LASTPOS);
RESULT = :TEMPSTR;
LASTPOS = :NEXTPOS + 1;
NEXTPOS = position(',', :AINPUT, LASTPOS);
suspend;
END
END
When you pass the SP the following list
CommaSeperatedList = 1,2,3,4
and call
SELECT * FROM SPLIT_STRING(:CommaSeperatedList)
the result will be :
RESULT
1
2
3
4
And can be used as follows:
SELECT * FROM MyTable where MyKeyField in ( SELECT * FROM SPLIT_STRING(:CommaSeperatedList) )
I ended up using a global temporary table in Firebird, inserting parameter values first and to retrieve results I use a regular JOIN instead of a WHERE ... IN clause. The temporary table is transaction-specific and cleared on commit (ON COMMIT DELETE ROWS).
Maybe you should wite it like this:
SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM1 , :MYPARAM2)
I don't think it's something that can be done. Are there any particular reason why you don't want to build the query yourself?
I've used this method a couple of times, it doesn't use parameters though. It uses a stringlist and it's property DelimitedText. You create a IDList and populate it with your IDs.
Query.SQL.Add(Format('MT_ID IN (%s)', [IDList.DelimitedText]));
You might also be interested in reading the following:
http://www.sommarskog.se/dynamic_sql.html
and
http://www.sommarskog.se/arrays-in-sql-2005.html
Covers dynamic sql with 'in' clauses and all sorts. Very interesting.
Parameters are placeholders for single values, that means that an IN clause, that accepts a comma delimited list of values, cannot be used with parameters.
Think of it this way: wherever I place a value, I can use a parameter.
So, in a clause like: IN (:param)
I can bind the variable to a value, but only 1 value, eg: IN (4)
Now, if you consider an "IN clause value expression", you get a string of values: IN (1, 4, 6) -> that's 3 values with commas between them. That's part of the SQL string, not part of a value, which is why it cannot be bound by a parameter.
Obviously, this is not what you want, but it's the only thing possible with parameters.
The answer from Yurish is a solution in two out of three cases:
if you have a limited number of items to be added to your in clause
or, if you are willing to create parameters on the fly for each needed element (you don't know the number of elements in design time)
But if you want to have arbitrary number of elements, and sometimes no elements at all, then you can generate SLQ statement on the fly. Using format helps.
SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM) instead of using MYPARAM with :, use parameter name.
like SELECT * FROM MATABLE
WHERE MT_ID IN (SELECT REGEXP_SUBSTR(**MYPARAM,'[^,]+', 1, LEVEL)
FROM DUAL
CONNECT BY REGEXP_SUBSTR(MYPARAM, '[^,]+', 1, LEVEL) IS NOT NULL))**
MYPARAM- '368134,181956'
If you are using Oracle, then you should definitely check out Tom Kyte's blog post on exactly this subject (link).
Following Mr Kyte's lead, here is an example:
SELECT *
FROM MATABLE
WHERE MT_ID IN
(SELECT TRIM(substr(text, instr(text, sep, 1, LEVEL) + 1,
instr(text, sep, 1, LEVEL + 1) -
instr(text, sep, 1, LEVEL) - 1)) AS token
FROM (SELECT sep, sep || :myparam || sep AS text
FROM (SELECT ',' AS sep
FROM dual))
CONNECT BY LEVEL <= length(text) - length(REPLACE(text, sep, '')) - 1)
Where you would bind :MYPARAM to '368134,181956' in your case.
Here is a technique I have used in the past to get around that 'IN' statement problem. It builds an 'OR' list based on the amount of values specified with parameters (unique). Then all I had to do was add the parameters in the order they appeared in the supplied value list.
var
FilterValues: TStringList;
i: Integer;
FilterList: String;
Values: String;
FieldName: String;
begin
Query.SQL.Text := 'SELECT * FROM table WHERE '; // set base sql
FieldName := 'some_id'; // field to filter on
Values := '1,4,97'; // list of supplied values in delimited format
FilterList := '';
FilterValues := TStringList.Create; // will get the supplied values so we can loop
try
FilterValues.CommaText := Values;
for i := 0 to FilterValues.Count - 1 do
begin
if FilterList = '' then
FilterList := Format('%s=:param%u', [FieldName, i]) // build the filter list
else
FilterList := Format('%s OR %s=:param%u', [FilterList, FieldName, i]); // and an OR
end;
Query.SQL.Text := Query.SQL.Text + FilterList; // append the OR list to the base sql
// ShowMessage(FilterList); // see what the list looks like.
if Query.ParamCount <> FilterValues.Count then
raise Exception.Create('Param count and Value count differs.'); // check to make sure the supplied values have parameters built for them
for i := 0 to FilterValues.Count - 1 do
begin
Query.Params[i].Value := FilterValues[i]; // now add the values
end;
Query.Open;
finally
FilterValues.Free;
end;
Hope this helps.
There is one trick to use reversed SQL LIKE condition.
You pass the list as string (VARCHAR) parameter like '~12~23~46~567~'
Then u have query like
where ... :List_Param LIKE ('%~' || CAST( NumField AS VARCHAR(20)) || '~%')
CREATE PROCEDURE TRY_LIST (PARAM_LIST VARCHAR(255)) RETURNS (FIELD1....)
AS
BEGIN
/* Check if :PARAM_LIST begins with colon "," and ands with colon ","
the list should look like this --> eg. **",1,3,4,66,778,33,"**
if the format of list is right then GO if not just add then colons
*/
IF (NOT SUBSTRING(:PARAM_LIST FROM 1 FOR 1)=',') THEN PARAM_LIST=','||PARAM_LIST;
IF (NOT SUBSTRING(:PARAM_LIST FROM CHAR_LENGTH(:PARAM_LIST) FOR 1)=',') THEN PARAM_LIST=PARAM_LIST||',';
/* Now you are shure thet :PARAM_LIST format is correct */
/ * NOW ! */
FOR SELECT * FROM MY_TABLE WHERE POSITION(','||MY_FIELD||',' in :PARAM_LIST)>0
INTO :FIELD1, :FIELD2 etc... DO
BEGIN
SUSPEND;
END
END
How to use it.
SELECT * FROM TRY_LIST('3,4,544,87,66,23')
or SELECT * FROM TRY_LIST(',3,4,544,87,66,23,')
if the list have to be longer then 255 characters then just change the part of header f.eg. like PARAM_LIST VARCHAR(4000)