Check table access for Oracle SQL statements in BLOB - sql

I have a table with a column containing SQL query (select statements only) in BLOB format.
I'm trying to figure out a way to analyse the SQL queries, and check which tables each SQL queries will be accessing.
Anyone done anything similar?
My thought right now is to create a PLSQL loop that:
Converts the BLOB to text to get the SQL query
Execute an explain plan for the query
Look at the result of the explain plan, and get all the lines with TABLE ACCESS
Edit: I'm on Oracle Database 11g Release 11.2.0.4.0 - 64bit if that helps, and have read-only access to the database, so I'll need to create a solution that's not intrusive to the DB.

This could be another simpler method.
Create dynamic views for those SQLs in the BLOB field.
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW YOUR_VIEW_NAME AS '||
utl_raw.cast_to_varchar2(dbms_lob.substr(blob_field));
Query user_dependencies to get the referenced table names.
SELECT referenced_name AS table_name
FROM user_dependencies
WHERE type = 'VIEW'
AND NAME = 'YOUR_VIEW_NAME'
AND referenced_type = 'TABLE';

So, here is how I have done it:
DECLARE
b BLOB;
c CLOB;
n NUMBER;
BEGIN
-- << loop_through_table >>
FOR o IN (SELECT TRIM(LABEL) lbl, REQUEST req FROM REQUEST_TABLE) LOOP
b := o.req;
IF ((b is null) OR (LENGTH(b)=0)) THEN
DBMS_OUTPUT.PUT_LINE(o.lbl || ' : No query found');
CONTINUE;
END IF;
-- Convert the BLOB to text to get the SQL query
DBMS_LOB.CREATETEMPORARY(c,TRUE);
n:=1;
-- << loop_through_blob >>
WHILE (n+32767<=LENGTH(b)) LOOP
DBMS_LOB.WRITEAPPEND(c,32767,UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(b,32767,n)));
n:=n+32767;
END LOOP;
DBMS_LOB.WRITEAPPEND(c,length(b)-n+1,UTL_RAW.CAST_TO_VARCHAR2(DBMS_LOB.SUBSTR(b,length(b)-n+1,n)));
-- Execute explain plan
execute immediate 'EXPLAIN PLAN SET STATEMENT_ID=''temp1'' FOR ' || c;
-- Look at the result of the explain plan, and get all the lines of OBJECT_TYPE='TABLE'
-- << loop_through_plan >>
FOR p IN ( SELECT OBJECT_NAME FROM PLAN_TABLE WHERE STATEMENT_ID='temp1' AND OBJECT_TYPE='TABLE') LOOP
DBMS_OUTPUT.PUT_LINE(o.lbl || ', ' || p.OBJECT_NAME);
END LOOP;
execute immediate 'DELETE FROM PLAN_TABLE WHERE STATEMENT_ID=''temp1'' ';
END LOOP;
DBMS_OUTPUT.PUT_LINE('-- Completed --');
END;

Related

Oracle sql developer| export from query to a table

Good day,
In DBeaver db manager we are able to transfer data directly from query to a table created by us. We right click>execute>export to a table.
Can anyone help me to do the same in oracle developer environment.
Here I have created a table in the same connection,and want to store query results into table.
Thank you
Right click on your table.
Choose Table - Copy.
Give it a name, and check 'Include Data'
You can see the code we're about to run by clicking on the SQL panel.
declare
l_sql varchar2(32767);
c_tab_comment varchar2(32767);
procedure run(p_sql varchar2) as
begin
execute immediate p_sql;
end;
begin
run('create table "HR".BEERS_COPY as select * from "HR"."BEERS" where '||11||' = 11');
begin
select comments into c_tab_comment from sys.all_TAB_comments where owner = 'HR' and table_name = 'BEERS' and comments is not null;
run('comment on table HR.BEERS_COPY is '||''''||REPLACE(c_tab_comment, q'[']', q'['']')||'''');
for tc in (select column_name from sys.all_tab_cols where owner = 'HR' and table_name = 'BEERS')
loop
for c in (select comments from sys.all_col_comments where owner = 'HR' and table_name = 'BEERS' and column_name=tc.column_name)
loop
run ('comment on column HR.BEERS_COPY.'||tc.column_name||' is '||''''||REPLACE(c.comments, q'[']', q'['']')||'''');
end loop;
end loop;
EXCEPTION
WHEN OTHERS THEN NULL;
end;
end;
So basically a CTAS + we grab the comments for you.
Disclaimer: I'm the product manager for SQL Developer and a Oracle employee.

Oracle - BULK COLLECT INTO VARRAY used with Bind Variables only collecting column headers

Quick Disclaimer:
First thing out of the way, I know the preferred way of handling dynamic SQL in Oracle now is the DBMS_SQL package but unfortunately my application team does not have the grants to execute these procs at the moment and I am hoping to get this quick workaround knocked out before our DBA team gets back to me. Also, this database is on Oracle 12c.
Script Goal: I recently developed a Stored Proc (let's call it Original) that uses values in a "control table" to make a large number of updates to certain columns in a database with many schemas and tables. This script I am struggling with now (let's call it Test) is meant to be a quick loop through those columns affected by Original so as to verify that everything worked expectedly. Ultimately, I want to output the top 5 results of each changed column and hand a spooled file to my testing team for validation.
The control_table used in both scripts has 4 columns and looks like this:
OWNER
TABLE_NAME
COLUMN_NAME
ALGORITHM
Schema1
TableA
ColumnA
Method1
Schema1
TableB
ColumnB
Method1
Schema2
TableC
ColumnC
Method2
An example of one of the tables that gets updated by Original (let's say for TableA above) would be:
OtherCol1
OtherCol2
ColumnA
OtherCol3
Ignored
Ignored
UpdatedData1
Ignored
Ignored
Ignored
UpdatedData2
Ignored
Ignored
Ignored
UpdatedData3
Ignored
Issue with Test script: I have the dynamic SQL - I believe - working as it needs and I have been trying to figure out how best to print the results of the EXECUTE IMMEDIATE command to output. In doing some reading, I found that BULK COLLECT INTO should allow me to store the results of the dynamic queries into a COLLECTION which I can then print with dbms_output. I have attempted to do this with both a TABLE and a VARRAY but in both cases when I print, I am finding that the data stored in my collection is the column header of my dynamic query instead of the query values! The only thing I can think that could be the problem is the combining of BULK COLLECT INTO with the USING command when I run the dynamic statement but I have seen nothing in the documentation to indicate that these two commands are incompatible and my Test procedure below compiles without issue (and even seems to run ok).
Test Script:
SET SERVEROUTPUT ON SIZE UNLIMITED;
DECLARE
l_script VARCHAR2(500);
l_errm VARCHAR2(64);
TYPE results IS VARRAY(5) OF VARCHAR2(250);
va_cols results; --Defining here with a VARRAY but I have also tried with a table
BEGIN
FOR c_col IN(
SELECT owner, table_name, column_name, algorithm FROM control_list)
LOOP
l_errm := NULL;
va_cols := NULL;
BEGIN
dbms_output.put_line('Column '|| c_col.column_name || ' of table ' || c_col.owner ||
'.' || c_col.table_name || ' used algorithm ' || c_col.algorithm);
l_script := 'SELECT :1 FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY';
dbms_output.put_line('Script sent to Exec Immediate: ' || l_script); --Print l_script for debugging
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols USING c_col.column_name, c_col.column_name;
dbms_output.put_line(va_cols(1));
dbms_output.put_line(va_cols(2));
dbms_output.put_line(va_cols(3));
dbms_output.put_line(va_cols(4));
dbms_output.put_line(va_cols(5));
EXCEPTION
WHEN OTHERS THEN
l_errm := SUBSTR(SQLERRM, 1, 64);
dbms_output.put_line(' ERROR: ' || l_errm || '. Skipping row');
CONTINUE;
END;
END LOOP;
END;
/
So my intended dbms_output of the script above is:
Column ColumnA of table Schema1.TableA used algorithm Method1
Script sent to Exec Immediate: SELECT :1 FROM SCHEMA1.TABLEA WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY
UpdatedData1
UpdatedData2
UpdatedData3
UpdatedData4
UpdatedData5
Instead, however, bizarrely, what I am getting when I run this is:
Column ColumnA of table Schema1.TableA used algorithm Method1
Script sent to Exec Immediate: SELECT :1 FROM SCHEMA1.TABLEA WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY
ColumnA
ColumnA
ColumnA
ColumnA
ColumnA
Has anyone seen this before and know what I am doing wrong? Thanks in advance!!
You can't use bind variables to change what columns you're referencing. You use bind variables to specify particular values at runtime. When you do
l_script := 'SELECT :1 FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE :2 IS NOT NULL FETCH FIRST 5 ROWS ONLY';
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols USING c_col.column_name, c_col.column_name;
you're telling Oracle that you want to select the literal string in the variable c_col.column_name. Not the column in the table by that name. Which is why every row returns that literal value.
You'd need to dynamically assemble the SQL statement with the column names, not try to use them as bind variables. So something like
l_script := 'SELECT ' || c_col.column_name ||
' FROM ' || c_col.owner || '.' || c_col.table_name ||
' WHERE ' || c_col.column_name || ' IS NOT NULL FETCH FIRST 5 ROWS ONLY';
EXECUTE IMMEDIATE l_script BULK COLLECT INTO va_cols;
This is approximately what you want. I outer cursor over tables and column to inspect that generate the dynamic SQL.
Inner loop reading the column values from the previous query
DECLARE
TYPE CurTyp IS REF CURSOR;
v_cursor CurTyp;
v_value VARCHAR2(200);
v_stmt_str VARCHAR2(200);
BEGIN
FOR c IN (
SELECT table_name, column_name FROM control_list)
LOOP
dbms_output.put_line('tab: '||c.table_name);
v_stmt_str := 'SELECT '||c.column_name||' FROM '|| c.table_name;
OPEN v_cursor FOR v_stmt_str;
LOOP
FETCH v_cursor INTO v_value;
EXIT WHEN v_cursor%NOTFOUND;
dbms_output.put_line('col: '||c.column_name||' val: '||v_value);
END LOOP;
END LOOP;
CLOSE v_cursor;
END;
/

How to convert every Table from a specific User to JSON Format using the "trick" provided by SQL Developer

I want to convert all tables from a specific user to JSON (or XML) Format. I've read about a "trick" mentioned by SQL Developer.
In other words, I already started to create a Procedure with two parameters:
p_format: The format (in my case it will be "json")
p_user: The username
As IDE I use Oracle SQL Developer and my database is an Oracle XE Database.
At first the procedure loops though all tables of the given user and in the loop, it should execute the following:
SELECT /*p_format*/ * FROM p_user || '.' || table
Unfortunately, I cannot use this SELECT Statement as mentioned above. I need to use the command EXECUTE IMMEDIATE <Statement>.
The next problem I faced was the following: I wanted to output the result of the EXECUTE IMMEDIATE command. Therefore I used the command EXECUTE IMMEDIATE <Statement> INTO <Variable>. After compiling the procedure and executing it, I stumpled across the following Error:
"inconsistent datatypes: expected %s got %s"
This is my code of the procedure:
CREATE OR REPLACE PROCEDURE EXPORT_TABLE_TO_FORMAT_FROM(p_format VARCHAR2, p_user VARCHAR2) IS
/***************************************************************************
Author:
Class:
School:
Date:
Function - EXPORT_TABLE_TO_JSON_FROM(p_user):
Displays the data of every table from a given User as JSON
Parameter: p_user ... User
***************************************************************************/
v_tableData VARCHAR2(32767);
v_sqlStatement VARCHAR2(200);
BEGIN
FOR tablerec IN (SELECT *
FROM ALL_TABLES
WHERE OWNER = p_user)
LOOP
v_sqlStatement := 'SELECT /*' || p_format || '*/ * FROM ' || p_user || '.' || tablerec.TABLE_NAME;
EXECUTE IMMEDIATE v_sqlStatement INTO v_tableData;
DBMS_OUTPUT.PUT_LINE (v_sqlStatement);
END LOOP;
END;
You can see that I loop though all tables of a given user and created a sql statement with p_format and p_user and with tablerec.TABLE_NAME.
The desired result should look exactly like that:
{"results":[{"columns":[{"name":"COUNTRY_ID","type":"CHAR"},
{"name":"COUNTRY_NAME","type":"VARCHAR2"},{"name":"REGION_ID","type":"NUMBER"}],"items":
[
{"country_id":"AR","country_name":"Argentina","region_id":2},
{"country_id":"AU","country_name":"Australia","region_id":3},
{"country_id":"BE","country_name":"Belgium","region_id":1},
{"country_id":"BR","country_name":"Brazil","region_id":2},
{"country_id":"CA","country_name":"Canada","region_id":2},
{"country_id":"CH","country_name":"Switzerland","region_id":1},
{"country_id":"CN","country_name":"China","region_id":3},
{"country_id":"DE","country_name":"Germany","region_id":1},
{"country_id":"DK","country_name":"Denmark","region_id":1},
{"country_id":"EG","country_name":"Egypt","region_id":4},
{"country_id":"FR","country_name":"France","region_id":1},
{"country_id":"IL","country_name":"Israel","region_id":4},
{"country_id":"IN","country_name":"India","region_id":3},
{"country_id":"IT","country_name":"Italy","region_id":1},
{"country_id":"JP","country_name":"Japan","region_id":3},
{"country_id":"KW","country_name":"Kuwait","region_id":4},
{"country_id":"ML","country_name":"Malaysia","region_id":3},
{"country_id":"MX","country_name":"Mexico","region_id":2},
{"country_id":"NG","country_name":"Nigeria","region_id":4},
{"country_id":"NL","country_name":"Netherlands","region_id":1},
{"country_id":"SG","country_name":"Singapore","region_id":3},
{"country_id":"UK","country_name":"United Kingdom","region_id":1},
{"country_id":"US","country_name":"United States of America","region_id":2},
{"country_id":"ZM","country_name":"Zambia","region_id":4},
{"country_id":"ZW","country_name":"Zimbabwe","region_id":4}]}]}
The JSON hint is specific to SQL Developer and SQLcl, not the database directly. So you need to run the entire thing within these tools.
Easiest way to do that is to have your script write a script that you can then run, eg
spool /tmp/get_all_json.sql
select 'select /*json*/ * from '||table_name||';'
from user_tables;
spool off
#/tmp/get_all_json.sql

Oracle SQL - UNION breaks when "table does not exist"

I am new to Oracle SQL (and not generally experienced in SQL anyway). I would appreciate it if I could get some help with my problem.
I need to query several tables and views (30 or so) and do it as a UNION across all and return a single result set. However, some of the tables may or may not exist. I know the exact names of all 30 tables/views to be queried. I may do other queries prior to doing the large UNION, but eventually, I must return a single result of all rows. I cannot "define" variables in my sql script.
These are constraints placed on my and beyond my control. In order to work with the existing workflow, I have to operate within these contraints.
The problem is that if any of the tables/views do not exist, then the entire UNION fails and I get no results at all. I get this at the first occurrence of the missing view/table:
SQL Error: ORA-00942: table or view does not exist
00942. 00000 - "table or view does not exist"
*Cause:
*Action:
I would like be able to handle this without breaking. I'd like to simply skip over missing tables and return the remaining rows (though I know this is poor design to try to query without knowing if the table exists). It's ok if I end up including error messages as these will be skipped by the parser eventually.
I have something like this. If any of the views do not exist, everything fails and I get no results:
select col1 || col2 || ... from view1
union
select col1|| col2 || ... from view2
union
select name from view3
.
.
select ... from view30;
I can switch to PL/SQL if that helps. I don't have much experience with PL/SQL but I can use it if it can solve the problem. I did some reading on PL/SQL but could not figure out some straightforward way of doing this.
Any help is highly appreciated. Let me know if I can provide any other details.
Thanks.
Colleagues don't say that you CAN'T do that - they are saying you SHOULDN'T, cause of the fundamental problems. But the world isn't perfect - it often gives you a saw and tells you to hammer a nail. There is a solution to your problem - it involves dynamic SQL and some custom PL/SQL code. Simplified solution:
Create a package and a function:
function does_tbl_exists(p_tbl_name in varchar2) return number is
l_stmt varchar2(60);
begin
l_stmt := 'select count(*) from ' ||p_tbl_name;
execute immediate l_stmt;
return 1;
exception
when others then
return 0 ;
end;
Created a test table TEST_TBL with some data. And then we build up our SQL statement in a pl/sql block:
declare
l_count number;
l_stmt varchar2(400);
l_tab_name_1 varchar2(30) := 'TEST_TBL';
l_tab_name_2 varchar2(30) := 'TEST_TBL2';
begin
if test_pck.does_tbl_exists(p_tbl_name => l_tab_name_1) = 1 then
l_stmt := 'select count(*) count_number from ' || l_tab_name_1;
end if;
if test_pck.does_tbl_exists(p_tbl_name => l_tab_name_2) = 1 then
if l_stmt is not null then
l_stmt := l_stmt || ' union all' || chr(10);
end if;
l_stmt := l_stmt || 'select count(*) count_number from ' || l_tab_name_2;
end if;
if l_stmt is not null then
l_stmt := 'select sum(tmp.count_number) from (' || l_stmt || ') tmp';
dbms_output.put_line(l_stmt);
execute immediate l_stmt into l_count;
dbms_output.put_line('count: '||l_count);
else
dbms_output.put_line('Nothing todo.');
end if;
end;
By this template, you can build up your final SQL statement.
But use this as a last resort - I think you should still go to higher-ups and talk with them, that their current "requirement" is "bad" and they should feel "bad" (Insert meme here).

Search a value in all tables in a connection (Sql developer)

I found many such questions but the answers where all using Stored Procedures.
I want an answer that uses purely a query in Oracle Sql Developer.
I have a value 'CORE_AO0001031_70_EMail_1' but not sure in which table. The number of tables and the data inside them are very huge.
Doesn't matter if the query is huge and will take time to execute. Is there any such query?
The reason for my asking a query is, I don't have privilege to create a Stored Procedure and I won't be given that privilege. Please help me with a query.
With an SQL you can't, as the queries are going to be dynamic. You have to execute a PL/SQL atleast.
Note: This is a Costly operation!
You can still attempt a full download of all you tables as spooling, and make PERL search into all files. In that case, you need a lot of disk space, but less harm(Just better than the Pl/SQL) to the database
DECLARE
TYPE TY_TABLE_NAMES IS TABLE OF VARCHAR2(30);
L_TABLE_NAMES TY_TABLE_NAMES;
TYPE TY_COLUMN_NAMES IS TABLE OF VARCHAR2(30);
L_COLUMN_NAMES TY_COLUMN_NAMES;
v_SCHEMA_NAME VARCHAR2(30) = 'SYSTEM'; --Your Schema Name
v_QUERY_STRING VARCHAR2(4000);
v_SEARCH_STRING VARCHAR2(4000) := 'CORE_AO0001031_70_EMail_1';
v_SEARCH_FLAG CHAR(1) := 'N';
BEGIN
SELECT ALL_TABLES
BULK COLLECT INTO L_TABLE_NAMES
WHERE OWNER = v_SCHEMA_NAME;
FOR I In 1..L_TABLE_NAMES.COUNT LOOP
SELECT COLUMN_NAME
BULK COLLECT INTO L_COLUMN_NAMES
FROM ALL_TAB_COLUMNS
WHERE TBALE_NAME = L_TABLE_NAMES(I)
AND OWNER = v_SCHEMA_NAME;
FOR J In 1..L_COLUMN_NAMES.COUNT LOOP
BEGIN
v_QUERY_STRING := 'SELECT ''Y'' FROM DUAL WHERE EXISTS (SELECT ''X'' FROM '||L_TABLE_NAMES(I)||' WHERE '||
||L_COLUMN_NAMES(J)|| ' LIKE ''%'|| v_SEARCH_STRING||'%'')';
EXCECUTE IMMEDIATE v_QUERY_STRING INTO v_SEARCH_FLAG;
WHEN NO_DATA_FOUND THEN
v_SEARCH_FLAG := 'N';
END;
IF(v_SEARCH_FLAG = 'Y') THEN
DBMS_OUTPUT.PUT_LINE(v_SEARCH_STRING || ' found in column '||L_COLUMN_NAMES(I)|| ' of table '||L_TABLE_NAMES(I));
BREAK;
END IF;
END LOOP;
IF(v_SEARCH_FLAG = 'Y') THEN
DBMS_OUTPUT.PUT_LINE('Done Searching!');
BREAK;
END IF;
END LOOP;
END;
/