Oracle PL/SQL - wildcard replacements with empty strings - oracle

To give you a little heads up. I am fairly new to the PL/SQL world.
Most of the text below is just to give you a little context.
Also, I have to use a function to generate the text on the fly. It is not an option to write the text into the db.
Anyway, I am working on a set of functions that will generate strings.
function f_nlg_concat(p_id in number) return varchar2 as
l_id number;
l_text_schaden varchar2(4000);
begin
l_id := p_id;
l_text := f_nlg(l_id);
l_text := f_nlg_replace(l_text, l_id);
end;
The above function is the one that concatinates all the strings. For this example I will only use one text (l_text). The l_text will be provided by the function:
f_nlg(p_id)
Now where it gets interesting is the second function:
f_nlg_replace(p_text,p_id)
This function basically queries via dynamic sql to find the right replacements for the wildcard. There are alot of wildcards.
A text might look like:
'I am a text with this <wildcard>.'
A view provides the right wildcard replacement. I am able to identify the right replacement by iterating over all the columns of the view by using this dynamic sql:
for cur in (SELECT atc.column_name
FROM all_tab_cols atc
WHERE lower(table_name) like 'v_nlg_wildcards'
AND owner = 'ME') loop
/*check if exist*/
l_query1 := 'select count(' || cur.column_name ||
') from ME.v_nlg_wildcards nw where nw.id =' ||
l_id;
execute immediate l_query1
into l_wildcard;
if l_wildcard = 1 then
l_query2 := 'select ' || cur.column_name ||
' from ME.v_nlg_wildcard nw where nw.id =' ||
l_id;
execute immediate l_query2
into l_baustein;
/*THIS IS WHERE I GOT A QUESTION*/
l_text := replace(l_text,
'<' || lower(cur.column_name) || '>',
l_wildcard);
else
continue;
end if;
end loop;
It works all perfect as long as there are wildcards to find. I case there is a NULL the replace wont work obviously.
But have you had any familiar cases in where you had to replace wildcards with empty strings?
Any ideas on how to still do the replacements?
So that there'll be no wildcards left in the text.
The whole text should still look like there is not wildcard left. The sentences are partly written that an empty replacement would fit as well.
Thank y'all in advance.
Cheers,
Sven

Related

Search string to match Data inside of all existing columns and rows from Views in Oracle SQL

The code below is looking for a string to match a column name. However I would like to search for a string to match Data (meaning, search on each existing column and row from all views - not column names). I want the results to show me all Views Names that contain that string on their Data. Hope this makes sense.
begin
dbms_output.put_line('Owner View name');
dbms_output.put_line('------------------------------ -------------------------------');
for r in (
select v.owner, v.view_name, v.text
from all_views v
where v.owner <> 'SYS'
)
loop
if lower(r.text) like '%my_String%' then
dbms_output.put_line(rpad(r.owner,31) || r.view_name);
end if;
end loop;
end;
I suggest you at least review some of the Oracle documentation. Your statement "Oracle SQL... i am using Oracle SQL Developer" indicates a lack of understanding. So lets clear some of that up. You are actually using 3 separate software tools:
Oracle SQL - all access to data being stored in your Oracle database.
Oracle Pl/SQL - the programming language, which tightly integrates with but is still separate from SQL. This is the language in which your script is written.
Oracle SQL Developer - an IDE within which you develop, run, etc run your Pl/SQL scripts and/or SQL statements.
Now as for your script. The syntax is fine and it will run, successfully retrieving all non sys owned views and printing the names of those containing the specified search string. However, as it currently stands it'll never find a match. Your match statement attempts to match a lower case string to a string that contains the character 'S' (upper case). There will never be a match. Also, keep in mind that within LIKE the underscore (_) is a single character wild card. You may need to escape it as "like '%my_ ...'". With these in mind we come to:
REVISED
The requirement to actually find the string in view columns completely changes things from your original query. Although the title does suggest that was actually the initial desire. What you want to accomplish is much more complex and cannot be done in SQL alone; it requires PL/SQL or an external language code. The reason is that you need run select but you don't know against what nor what columns nor even how many columns area needed. For that you need to identify the view, isolate the viable columns and generate the appropriate SQL dynamically then actually execute that sql and check the results.
Two approaches come to mind: Parse the view to extract the columns (something I would even consider doing in PL/SQL) or join ALL_VIEWS with ALL_TAB_COLUMNS (All table columns). That we'll do. The majority of the code will be constructing the SQL and the necessary error handling dynamic SQL essentially forces on you. I've created this a a procedure with 2 parameters: the target string, and the schema.
create or replace procedure find_string_in_view(
schema_name_in varchar2
, target_string_in varchar2
)
is
--- set up components for eventual execution
k_new_line constant varchar2(2) := chr(10);
k_base_statement constant varchar2(50) :=
q'!select count(*) from <view_name> where 1=1 and !' || k_new_line;
k_where_statement constant varchar2(50) :=
q'! <column_name> like '%<target>%' !' || k_new_line;
k_limit_statement constant varchar2(20) :=
' ) and rownum < 2';
k_max_allowed_errors constant integer := 3;
--- cursor for views and column names
cursor c_view_columns is
(select av.owner,av.view_name , atc.column_name
, lead(atc.column_name) over (partition by av.view_name order by atc.column_id) next_col
, lag(atc.column_name) over (partition by av.view_name order by atc.column_id) prev_col
from all_views av
join all_tab_columns atc
on ( atc.owner = av.owner
and atc.table_name = av.view_name
)
where av.owner = upper(schema_name_in)
and atc.data_type in
('CHAR', 'NCHAR', 'VARCHAR2','NVARCHAR2','VARCHAR','NVARCHAR')
) ;
--- local variables
m_current_view varchar2(61);
m_sql_errors integer := 0;
m_where_connector varchar(2);
m_sql_statement varchar2(4000);
-- local helper function
function view_has_string
return boolean
is
l_item_count integer := 0;
begin
execute immediate m_sql_statement into l_item_count;
return (l_item_count > 0);
exception
when others then
dbms_output.put_line(rpad('-',61,'-') || k_new_line);
dbms_output.put_line('Error processing:: ' || m_current_view);
dbms_output.put_line('Statement::' || k_new_line || m_sql_statement);
dbms_output.put_line(sqlerrm);
m_sql_errors := m_sql_errors + 1;
if m_sql_errors >= k_max_allowed_errors
then
raise_application_error(-20199,'Maximun errors allowed reach. Terminating');
end if;
return false;
end view_has_string;
begin -- MAIN --
dbms_output.put_line('Owner View name');
dbms_output.put_line('------------------------------ -------------------------------');
for rec in c_view_columns
loop
if rec.prev_col is null
then
m_current_view := rec.owner || '.' || rec.view_name;
m_sql_statement := replace(k_base_statement,'<view_name>',m_current_view);
m_where_connector := ' (';
end if;
m_sql_statement := m_sql_statement || m_where_connector
|| replace(k_where_statement,'<column_name>',rec.column_name);
m_where_connector := 'or' ;
if rec.next_col is null
then
m_sql_statement := replace(m_sql_statement,'<target>',target_string_in);
m_sql_statement := m_sql_statement || k_limit_statement;
if view_has_string
then
dbms_output.put_line(rpad(rec.owner,31) || rec.view_name);
end if;
end if;
end loop;
end find_string_in_view;
select v.owner, v.view_name, v.text
from all_views v
where v.owner <> 'SYS' and lower(v.text) like '%my_String%'
one SQL do this?

How can I compile PL/SQL source saved in table?

How do I compile PL/SQL source code that I currently have saved in an Oracle table? (I'm making a copy of the source code from the USER_SOURCE view, then I'm deleting those objects and want to restore from my saved copy.)
I'm sure there is an easy way, but I'm just not entering the right search terms.
Try this:
declare
text varchar2(4000);
begin
select code into text from bkp_table;
execute immediate 'create or replace ' || text;
end;
/
Ok this works if all lines of code are stored in single line. If you want to execute code that is stored in multiple lines you should go for something like:
declare
text varchar2(32767);
begin
select listagg(text, ' ') within group (order by line) into text from all_source where name = 'MYPROC';
execute immediate 'create or replace ' || text;
end;
/
Problem starts when 32767 characters is too few. In such case this may be a solution:
declare
text clob;
begin
for x in (select text from all_source where name = 'LONGTEST') loop
text := text || x.text;
end loop;
execute immediate 'create or replace ' || text;
end;
/
Please also have a look on that why it is a bit odd thing.
EDIT
As suggested changed to dbms_lob and in that case clob needs to be initialised:
declare
text clob := ' ';
begin
for x in (select text from all_source where name = 'LONGTEST') loop
dbms_lob.append(text, x.text);
end loop;
execute immediate 'create or replace ' || text;
end;
/

How to handle dynamic variable string value in single line

This is my PL/SQL block. When i execute it the result of 'V_QUERY' variable breaks into two lines like:
'%[CDATA[/documents/%/4min.png
/%'.
How can i get it in one line like below: enter image description here
'%[CDATA[/documents/%/4min.png/%'
.
declare
cursor c1 is
select rtrim(ltrim(substr(typesettings, 7), '"'), '"') as typesettings
from trashentry
where rownum < 2;
v_query varchar2(32767);
begin
for r1 in c1
loop
v_query := '''%[CDATA[/documents/%/' || r1.typesettings || '/%''';
dbms_output.put_line(v_query);
end loop;
end;
Most likely you need to trim chr(10) from the end of typesettings. It may not hurt to trim chr(13) as well, on the chance it may be present too.
Assuming there aren't any double-quotes in your input string that you need to keep, you can achieve several things at once: Instead of rtrim(ltrim(...)) (which should be a trim(...) anyway, it does the same thing and is simpler and only calls one function instead of two), you could use translate:
select translate(substr(typesettings, 7), 'x"' || chr(10) || chr(13), 'x') as typesettings
.......
Notice the x in the last two parameters; it doesn't really do anything, it just works around a limitation of the translate function (which in turn is due to Oracle's treatment of empty strings as NULL). In this example, translate will replace every x in the input string with x, and every occurrence of ", chr(10) and chr(13) will be simply deleted.

How can this piece of PLSQL be made to compile?

I need to return the names of employees in string format for all those employees whose manager ID depends on the passed parameter. When I compile the function I get an error. Here is the function code:
create or replace function Employee(v_manid IN employees.manager_id%type)
return varchar2
AS
cursor cur_emp is select last_name from employees where manager_id = v_manid;
v_names varchar2(10);
begin
for emp_rec in cur_emp
loop
v_name = v_name || emp_rec.last_name ||', ';
end loop;
return v_name
end;
/
The error is:
Error(8,8): PLS-00103: Encountered the symbol "=" when expecting one
of the following: := . ( # % ; Error(8,44): PLS-00103:
Encountered the symbol ";" when expecting one of the following: )
, * & - + / at mod remainder rem and or ||
Could anyone help me with this?
As stated in the other answers the reason why your function won't compile is threefold.
You've declared the variable v_names and are referencing it as v_name.
The assignment operator in PL/SQL is :=, you're using the equality operator =.
You're missing a semi-colon in your return statement; it should be return v_name;
It won't stop the function from compiling but the variable v_names is declared as a varchar2(10). It's highly unlikely that when a manager with multiple subordinates all their last names will fit into this. You should probably declare this variable with the maximum size; just in case.
I would like to add that you're doing this a highly inefficient way. If you were to do the string aggregation in SQL as opposed to a PL/SQL loop it would be better. From 11g release 2 you have the listagg() function; if you're using a version prior to that there are plenty of other string aggregation techniques to achieve the same result.
create or replace function employee ( p_manid in employees.manager_id%type
) return varchar2 is
v_names varchar2(32767); -- Maximum size, just in case
begin
select listagg(lastname, ', ') within group ( order by lastname )
into v_names
from employees
where manager_id = p_manid;
return v_names;
exception when no_data_found then
return null;
end;
/
Please note a few other changes I've made:
Prepend a different letter onto the function parameter than the variable to make it clear which is which.
Add in some exception handling to deal with there being no data for that particular manager.
You would have returned , if you had no data I return NULL. If you want to return a comma instead simply put this inside the exception.
Rather than bother to create a cursor and loop through it etc I let Oracle do the heavy lifting.
It's rather curious that you would want to return a comma delimited list as there is little that you would be able to do with it in Oracle afterwards. It might be more normal to return something like an array or an open cursor containing all the surnames. I assume, in this answer, that you have a good reason for doing what you are.
There are a couple of things to be noted.
Declared as v_names but used as v_name
Assignemnt should be like v_name := v_name || emp_rec.last_name ||
', ';
v_name is declared with size of 10, it would be too small and would
give an error when you execute, so you could declare as
v_name employees.last_name%TYPE;
You could create your function as
CREATE OR REPLACE FUNCTION employee (v_manid IN employees.manager_id%TYPE)
RETURN VARCHAR2
AS
v_name employees.last_name%TYPE;
CURSOR cur_emp
IS
SELECT last_name
FROM employees
WHERE manager_id = v_manid;
BEGIN
FOR emp_rec IN cur_emp
LOOP
v_name := v_name || emp_rec.last_name || ', ';
END LOOP;
RETURN v_name;
END;
/
I guess you should use := instead of =
like
v_name := v_name || emp_rec.last_name ||', ';
one more thing you also need to add semicolon ; at the end of return v_name like
return v_name;

Searching and replacing brackets in a string in Oracle 10G

I am currently developing a function that is meant to execute dynamically created SQL statements. This is done by concatenating the columns and fetching them via cursors. The problem is that when there is a function with a comma between its arguments, the concat concatenates the contents of the functions inclusive.
Is it possible to skip contents of every bracket found in a string using REGEXP_SUBTR or REGEXP_REPLACE?
Many thanks in advance for your prompt and kind suggestions.
-- strips out the select list
src_str := REGEXP_SUBSTR(v_sql, 'SELECT ([[:graph:]]+\ ?){1,1000000}/?');
-- Replace the commas in the select list with the concat symbol for concatenation
rep_str := REGEXP_REPLACE(src_str, ', ', p_dot);
-- Replace the select list with the replace string
v_query := REPLACE(v_sql, src_str, rep_str);
v_sql := select a, b, to_char(sysdate, 'dd/mm/yyyy') from demo;
p_dot := '||'',''||';
currently, it returns:
select a || ',' || b || ',' || to_char(sysdate || ',' || 'dd/mm/yyyy') from demo
but should return something like:
select a || ',' || b || ',' || to_char(sysdate, 'dd/mm/yyyy') from demo
Many thanks Rene, your query worked but I have one more question and here it is
for i in 1 .. p_arglist.count
loop
-- Search for : in the query
src_sym := REGEXP_SUBSTR(v_query, ':[[:graph:]]+\ ?', i,i);
-- Replace the : with each value of p_arglist passed
v_querymult := REGEXP_REPLACE(v_query, src_sym , p_arglist(i),i,i);
end loop;
return v_query;
where p_arglist is a varchar2 varray
p_arglist := ('demo#demo.com','2001')
v_query := 'SELECT A, B, C FROM DEMO WHERE USERID = :USERID AND YEAR = :YEAR';
Currently, it returns
v_query := SELECT A, B, C FROM DEMO WHERE USERID = :USERID AND YEAR = 2001
and skips the first in the list which is the userid.
many thanks for your anticipated help
Have you thought about using DBMS_SQL, that should parse the SQL and allow you to bind variables.
See these links for further reading
Oracle Docs
Ask Tom Example
something like this should do if I understood your requirement correctly:
-- multiple replacements to accomodate for functions with more
-- than two parameters (and accordingly more than one comma)
src_str := regexp_replace(src_str, '(\([^)]+),', '\1##comma-in-function##');
src_str := regexp_replace(src_str, '(\([^)]+),', '\1##comma-in-function##');
src_str := regexp_replace(src_str, '(\([^)]+),', '\1##comma-in-function##');
-- replace the left-over commas
src_str := replace(src_str, ', ', p_dot);
-- turn commas within function call back to commas:
src_str := replace(src_str, '##comma-in-function##', ',');

Resources