DBMS_SQL.EXECUTE not giving output when the SQL have DBMS_XMLGEN.GETXML - oracle

I am generating a dynamic SQL which generates XML data, using SELECT DBMS_XMLGEN.GETXML, as the output have special characters,
which are not supported in XML. I tried and tested SQL in separately, It is working as expected.
I am not able to fetch the data, when I call it using Dynamic SQL.
I have used DBMS_SQL.DESC_TAB2 as it is treating whole SQL starting DBMS_XMLGEN.GETXML as one column.
--Snippet
Declaration
SelectCursorId NUMBER; --For Dynamic SQL binding
RowProcessed INTEGER; --For Dynamic SQL binding
MstrSeqNbr NUMBER := 1;
ColumnCount NUMBER; --For Dynamic SQL binding
RecordSqrNbr NUMBER := 0; --For Dynamic SQL binding
ColumnDescTbl DBMS_SQL.DESC_TAB2; --dbms_sql.desc_tab2
ColumnValue VARCHAR2(4000); --For Dynamic SQL binding
DymanicSQLCols VARCHAR2(4000); -- For debugging purpose, columns returned
SelectSQL VARCHAR2(6000);
BEGIN
--Snippet
SelectSQL := 'SELECT DBMS_XMLGEN.GETXML('SELECT MRQ.BatchNBR AS Batch_NUMBER,
MRQ.BatchRUNSEQNBR AS Batch_RUN_INSTANCE,
MRQ.BatchRUNDATE AS RUN_DATE,
MRQ.SPDATE AS POST_DATE,
MRQ.BatchNAME AS Batch_NAME,
MRQ.RPTNAME AS DATABASE_NAME,
MRQ.EFFDATE AS RUN_TIME,
MRQ.BatchSTARTDATE AS ELAPSED_TIME ,
CURSOR ( SELECT MRI.MSTREPORTRECSEQNBR AS RECORD_SEQUENCE_NUMBER,
MRI.RTTEXT1VC100 AS COUNTRY_NAME,
MRI.RTTEXT2VC100 AS CURRENCY_USED,
MRI.RTTEXT1VC50 AS COUNTRY_SHORT_CODE,
MRI.RTNUM1P0 AS ISO_CURRENCY_CODE
FROM MasterReporting MRI
WHERE BatchNbr = MR.BatchNbr
AND BatchRUNSEQNBR = MR.BatchRUNINSTANCE
ORDER BY MSTREPORTRECSEQNBR )Record
FROM BatchRUNHIST MR , MastRptSeqDtl MRQ
WHERE MR.BatchNbr = 100
AND MR.BatchRUNINSTANCE IN( 67)
AND MRQ.BatchRUNSEQNBR = MR.BatchRUNINSTANCE
AND MR.BatchRunStatCD =''COMPL''
')
FROM DUAL ';
SelectCursorId := DBMS_SQL.OPEN_CURSOR; --Pass
DBMS_SQL.PARSE ( SelectCursorId, SelectSQL, DBMS_SQL.NATIVE); --Pass
DBMS_SQL.DESCRIBE_COLUMNS2( SelectCursorId, ColumnCount, ColumnDescTbl); --Pass
** However ColumnDescTbl(1).col_name is only giving following, not sure if this is the issue
DBMS_XMLGEN.GETXML('SELECT MRQ.BatchNBRASBatch_NUMBER, MRQ.BatchRUNSEQNBRASBatch_RUN_INSTANCE, MRQ.BatchRUNDATEASRUN_DATE, MRQ.SPDATEASPOST_DATE, MRQ.BatchNAMEASBatch_NAME, MRQ.RPTNAMEASDATABAS
Next step also passes
For k in 1..ColumnCount LOOP
DBMS_SQL.DEFINE_COLUMN(SelectCursorId, k, ColumnValue, 4000); --Pass
END LOOP;
RowProcessed := DBMS_SQL.EXECUTE(SelectCursorId);
--Passes but gives 0 as output,
--Whereas, running the SQL separately gives you one row of XML data.
Minimum One row of XML Data should be returned.

From the documentation for dbms_sql.execute:
The return value is only valid for INSERT, UPDATE, and DELETE statements; for other types of statements, including DDL, the return value is undefined and must be ignored.
As your statement is a select the RowProcessed return value has no meaning; it isn't significant that you're seeing zero there.
You need to do a fetch_rows step after that, or change it to execute_and_fetch. Either way you then need to process the fetched data, of course.
(The col_name looks like a non-issue, it's just an automatically generated name/alias. If you change your dynamic statement to do ...) AS my_col_alias FROM DUAL then the col_name will be reported as MY_COL_ALIAS.)

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?

Creating SQL-Injection proof dynamic where-clause from collection in PL/SQL

I need to execute a query where the where-clause is generated based on user input. The input consists of 0 or more pairs of varchar2s.
For example:
[('firstname','John')
,('lastname','Smith')
,('street','somestreetname')]
This would translate into:
where (attrib = 'firstname' and value = 'John')
and (attrib = 'lastname' and value = 'Smith')
and (attrib = 'street' and value = 'somestreetname')
This is not the actual data structure as there are several tables but for this example lets keep it simple and say the values are in 1 table. Also I know the parentheses are not necessary in this case but I put them there to make things clear.
What I do now is loop over them and concatinate them to the SQL string. I made a stored proc to generate this where-clause which might also not be very secure since I just concat to the original query.
Something like the following, where I try to get the ID's of the nodes that correspond with the requested parameters:
l_query := select DISTINCT n.id from node n, attribute_values av
where av.node_id = n.id ' || getWhereClause(p_params)
open l_rc
for l_query;
fetch l_rc bulk collect into l_Ids;
close l_rc;
But this is not secure so I'm looking for a way that can guaranty security and prevent SQL-Injection attacks from happening.
Does anyone have any idea on how this is done in a secure way? I would like to use bindings but I don't see how I can do this when you dont know the number of parameters.
DB: v12.1.0.2 (i think)
It's still a bit unclear and generalised, but assuming you have a schema-level collection type, something like:
create type t_attr_value_pair as object (attrib varchar2(30), value varchar2(30))
/
create type t_attr_value_pairs as table of t_attr_value_pair
/
then you can use the attribute/value pairs in the collection for the bind:
declare
l_query varchar2(4000);
l_rc sys_refcursor;
type t_ids is table of number;
l_ids t_ids;
l_attr_value_pairs t_attr_value_pairs;
-- this is as shown in the question; sounds like it isn't exactly how you have it
p_params varchar2(4000) := q'^[('firstname','John')
,('lastname','Smith')
,('street','somestreetname')]^';
begin
-- whatever mechanism you want to get the value pairs into a collection;
-- this is just a quick hack to translate your example string
select t_attr_value_pair(rtrim(ltrim(
regexp_substr(replace(p_params, chr(10)), '(.*?)(,|$)', 1, (2 * level) - 1, null, 1),
'[('''), ''''),
rtrim(ltrim(
regexp_substr(replace(p_params, chr(10)), '(.*?)(,|$)', 1, 2 * level, null, 1),
''''), ''')]'))
bulk collect into l_attr_value_pairs
from dual
connect by level <= regexp_count(p_params, ',') / 2 + 1;
l_query := 'select DISTINCT id from attribute_values
where (attrib, value) in ((select attrib, value from table(:a)))';
open l_rc for l_query using l_attr_value_pairs;
fetch l_rc bulk collect into l_ids;
close l_rc;
for i in 1..l_ids.count loop
dbms_output.put_line('id ' || l_ids(i));
end loop;
end;
/
although it doesn't need to be dynamic with this approach:
...
begin
-- whatever mechamism you want to get the value pairs into a collection
...
select DISTINCT id
bulk collect into l_ids
from attribute_values
where (attrib, value) in ((select attrib, value from table(l_attr_value_pairs)));
for i in 1..l_ids.count loop
dbms_output.put_line('id ' || l_ids(i));
end loop;
end;
/
or with a join to the table collection expression:
select DISTINCT av.id
bulk collect into l_ids
from table(l_attr_value_pairs) t
join attribute_values av on av.attrib = t.attrib and av.value = t.value;
Other collection types will need different approaches.
Alternatively, you could still build up your where clause with one condition per attribute/value pair, while still making them bind variables - but you would need two levels of dynamic SQL, similar to this.

Oracle PL/SQL - parameterizing SAMPLE clause in SELECT statement

I have a Oracle related question. I would like to select a random sample out of a view or table in such a way that the SAMPLE clause is parameterized.
Given the following table.
CREATE TABLE FOO AS
(SELECT LEVEL AS ID
FROM DUAL
CONNECT BY LEVEL < 101
);
The following construct works, using a literal parameter in the SAMPLE clause.
SELECT ID FROM FOO SAMPLE (15); -- this will get a 15% sample
However,
DECLARE
N NUMBER := 50;
BEGIN
FOR r IN
( SELECT ID FROM FOO SAMPLE (N) -- <<< this won't work
)
LOOP
DBMS_OUTPUT.PUT_LINE( r.ID );
END LOOP;
END;
This block blows up when we put a parameter in the SAMPLE clause. It compiles and works if we put it a literal.
But if it is a variable, I get the following:
ORA-06550: line 5, column 33:
PL/SQL: ORA-00933: SQL command not properly ended
Any ideas? I'm racking by brains where the syntax gets broken.
The syntax does not allow a variable there.
One workaround would be to construct the SELECT statement dynamically. For example:
declare
l_rc sys_refcursor;
n number := 5;
begin
-- replace "mtl_system_items" with your table...
open l_rc FOR 'select count(*) from mtl_system_items sample (' || n || ')';
-- replace call to RETURN_RESULT with whatever processing you want
DBMS_SQL.RETURN_RESULT(l_rc);
end;

Creating a dynamic script from dynamic SQL

I've searched a lot of Q-A's but I haven't found quite what I'm looking for. I'm looking to create a procedure that generates a transport script (but does NOT execute it) for two different lookup tables in a dynamic way.
I've been able to create the sql select statement using:
DECLARE
testertable VARCHAR2(320) := 'LOOKUP_TABLE';
testerid VARCHAR2(320) := '999';
testerfield VARCHAR2(320) := 'id_no';
type nameArray IS TABLE OF VARCHAR2(60);
type typeArray IS TABLE OF VARCHAR2(60);
data_nameA nameArray := nameArray();
data_typeA typeArray := typeArray();
i NUMBER;
CURSOR c_curse IS SELECT column_name, data_type
FROM user_tab_columns
WHERE table_name = testertable;
i := 0;
FOR n IN c_curse LOOP
i := i + 1;
data_nameA.EXTEND(1);
data_typeA.EXTEND(1);
data_nameA(i) := n.column_name;
data_typeA(i) := n.data_type;
END LOOP;
From there, I'm able to create the select statement. Then I want to use that statement to open a query, and populate insert statements, using data_typeA(i) as a reference to make sure quotes are input as needed.
This is part of a larger transport function that I'm not allowed to modify, my task is to expand it to include these two lookup tables, which may or may not exist for each transported system.
Hopeful output would be a CLOB or other text field like this:
-- transport for lookup 'LOOKUP_TABLE'
Insert into LOOKUP_TABLE VALUES (value1, 'value2', value3), (value4, 'value5', value6), (value7, 'value8', value9);
Thank you.

Oracle PL\SQL Null Input Parameter WHERE condition

As of now I am using IF ELSE to handle this condition
IF INPUT_PARAM IS NOT NULL
SELECT ... FROM SOMETABLE WHERE COLUMN = INPUT_PARAM
ELSE
SELECT ... FROM SOMETABLE
Is there any better way to do this in a single query without IF ELSE loops. As the query gets complex there will be more input parameters like this and the amount of IF ELSE required would be too much.
One method would be to use a variant of
WHERE column = nvl(var, column)
There are two pitfalls here however:
if the column is nullable, this clause will filter null values whereas in your question you would not filter the null values in the second case. You could modify this clause to take nulls into account but it turns ugly:
WHERE nvl(column, impossible_value) = nvl(var, impossible_value)
Of course if somehow the impossible_value is ever inserted you will run into some other kind of (fun) problems.
The optimizer doesn't understand correctly this type of clause. It will sometimes produce a plan with a UNION ALL but if there are more than a couple of nvl, you will get full scan even if perfectly valid indexes are present.
This is why when there are lots of parameters (several search fields in a big form for example), I like to use dynamic SQL:
DECLARE
l_query VARCHAR2(32767) := 'SELECT ... JOIN ... WHERE 1 = 1';
BEGIN
IF param1 IS NOT NULL THEN
l_query := l_query || ' AND column1 = :p1';
ELSE
l_query := l_query || ' AND :p1 IS NULL';
END IF;
/* repeat for each parameter */
...
/* open the cursor dynamically */
OPEN your_ref_cursor FOR l_query USING param1 /*,param2...*/;
END;
You can also use EXECUTE IMMEDIATE l_query INTO l_result USING param1;
This should work
SELECT ... FROM SOMETABLE WHERE COLUMN = NVL( INPUT_PARAM, COLUMN )

Resources