Why does this PL/SQL not find a table? - oracle

I have 2 table DOQUANGDUC1 and DOQUANGDUC, in table DOQUANGDUC list table in database I need check:
declare
BEGIN
FOR x IN (select table_name,column_name from DOQUANGDUC) LOOP
INSERT INTO DOQUANGDUC1 (table_name,column_name)
SELECT count(x.column_name),column_name FROM x.table_name GROUP BY column_name having count(x.column_name) >2;
END LOOP;
END;
I run sql error
Error at line 1
ORA-06550: line 9, column 57:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 8, column 10:
PL/SQL: SQL Statement ignored
I am sure two table already use.

PL/SQL: ORA-00942: table or view does not exist
The cause of the error message is this line:
FROM x.table_name
This is telling Oracle to query a table called table_name in a schema called X and that does not exist. Hence the failure.
I think what you're trying to do is loop through a list of tables and columns and count how many values each column has, and insert the results into another table. You can't do that with pure SQL, you need to use dynamic SQL to format the statement.
declare
stmt varchar2(32767);
begin
for x in (select table_name, column_name from doquangduc) loop
stmt := 'insert into doquangduc1(table_name, column_name, col_count) '
||' select '''||x.table_name||''', '''||x.column_name||''', count('||x.column_name||') '
||' from '||x.table_name
||' having count('||x.column_name||') > 2';
execute immediate stmt;
end loop;
end;
/
Here is a working demo on db<>fiddle.
Note that I have changed the insert statement to populate three columns in doquangduc1: correct this to fit your actual table structure (which you haven't posted).
It seems that the CMS_CIF_MUREX_NAME table has 292 records I add 1 duplicate record when running your command it shows the result column col_count = 293
My interpretation of your requirement is to count how many rows are populated for each column. Maybe that is not your actual requirement. Such misunderstandings are always a risk when you post a piece of code and expect us to reverse engineer your business logic. It is easier if you tell us what you're actually trying to achieve.
If you want counts for each distinct value in the column you can add a GROUP clause before the HAVING clause in my query.
'group by '||x.column_name ||
You may also want to add the value to the records inserted into doquangduc1, so you know which value has which count.

Approaches to Inserting into a Table through an Implicit Cursor or a Select Statement
Our Requirement:
We are going through the records in doquangduc and finding instances where the column_name appears more than twice.
Next, we are placing these records in another table, doquangduc1, while preserving the counts (placing them in doquangduc1).
Ultimately, these records are needed so they can be viewed.
Comments about this requirement:
It is not really clear that we really need to perform a transformation of the table, doquangduc, and then insert into another table to view the resulting records. This answer assumes this is necessary.
We create and execute DDL for tables needed first.
CREATE TABLE doquangduc
AS
SELECT
table_name,
column_name
FROM
user_tab_columns;
We create the table, doquangduc1, and make sure there is a column called col_count:
CREATE TABLE doquangduc1
AS
SELECT
table_name,
column_name,
1 col_count
FROM
user_tab_columns
WHERE
table_name IS NULL;
We sort through the doquangduc table to identify scenarios you have identified.
BEGIN
FOR x IN (
WITH qry AS (
SELECT
table_name,
column_name,
COUNT(1)
OVER(PARTITION BY column_name) AS col_count
FROM
user_tab_columns
)
SELECT
table_name,
column_name,
col_count
FROM
qry
WHERE
col_count > 2
) LOOP
INSERT INTO doquangduc1 (
table_name,
column_name,
col_count
) VALUES (
x.table_name,
x.column_name,
x.col_count
);
END LOOP;
END;
As various people have commented, do we really need an implicit cursor to perform this insert into the table, doquangduc1, so that we can view the results?
We could just perform a INSERT INTO with a SELECT statement to more efficiently insert these records into doquangduc1:
INSERT INTO doquangduc1
WITH qry AS (
SELECT
table_name,
column_name,
COUNT(1)
OVER(PARTITION BY column_name) AS col_count
FROM
user_tab_columns
)
SELECT
table_name,
column_name,
col_count
FROM
qry
WHERE
col_count > 2;

Related

Data not inserting to destination table

I have the following block of PL-SQL code in Oracle:
DECLARE TAB VARCHAR(100);
COL VARCHAR(100);
CURSOR C_COLS IS
select DISTINCT table_name, column_name
from all_tab_columns
where OWNER = 'MyDB' AND DATA_TYPE LIKE '%VARCHAR%';
BEGIN
OPEN C_COLS;
LOOP
FETCH C_COLS INTO TAB, COL;
EXIT WHEN C_COLS%notfound;
INSERT INTO TargetTable (TABLE_NAME, COLUMN_NAME, COLUMN_VALUE)
SELECT DISTINCT TAB,
COL,
(SELECT COL FROM TAB)
FROM TAB
WHERE REGEXP_LIKE(COL, '([ABCDEFGHIJKLMNOPQRSTUVWXYZ])\d\d\d\d\d\d([ABCDEFGHIJKLMNOPQRSTUVWXYZ])', 'ix');
END LOOP;
CLOSE C_COLS;
END;
The idea is to determine which tables in my rather large database contain a certain pattern of data and to find them.
So I want to return three columns: TableName, ColumnName, Value of ColumnName.
The above runs but returns no data and I can't understand why. The query in the cursor returns results, and if I hard code the table values into a simple select statement containing my Regex, I get results. I just want one result set that contains the thousands of results I expect.
Could it be the (SELECT COL FROM TAB) I'm using to dynamically find the column_value? I wasn't sure if I could express it this way.
If you want to select columns dynamically you may wish to try dynamic SQL.
DECLARE
w_sql VARCHAR2(32767);
BEGIN
DBMS_OUTPUT.enable(32767);
FOR s_cols IN (
select DISTINCT
table_name
, column_name
from all_tab_columns
where owner = 'MyDB'
AND data_type LIKE '%VARCHAR%'
)
LOOP
w_sql := q'!
INSERT
INTO TargetTable (TABLE_NAME, COLUMN_NAME, COLUMN_VALUE)
SELECT DISTINCT
':TAB'
, ':COL'
, :COL
FROM :TAB
WHERE REGEXP_LIKE(:COL, '([ABCDEFGHIJKLMNOPQRSTUVWXYZ])\d\d\d\d\d\d([ABCDEFGHIJKLMNOPQRSTUVWXYZ])', 'ix')
!';
w_sql := REPLACE(w_sql, ':TAB', s_cols.table_name);
w_sql := REPLACE(w_sql, ':COL', s_cols.column_name);
EXECUTE IMMEDIATE w_sql;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('Error for SQL :'|| w_sql ||'; error is :'|| SQLERRM);
END;

"ORA-01007: variable not in select list" when no rows are returned by EXECUTE IMMEDIATE

I have a procedure which receives as parameter a where clause (i.e. where col1 = 1). I am using this clause to search in some tables using an EXECUTE IMMEDIATE statement and the result to be inserted into a nested table, and than be displayed.
The procedure works fine if any data is found but in case no data is found, then the above error is thrown.
Can someone explain what cause this error, please?
Here is the procedure:
create or replace procedure prc_checks(pi_where varchar2) as
cursor c_tables is
select object_name,
case object_name
when 'XP_IMPORT_MW' THEN 99999999
when 'XP_IMPORT_MW_ARCH' THEN 99999998
else TO_NUMBER(SUBSTR(object_name, -8, 8))
end to_order
from dba_objects
where object_type = 'TABLE'
and object_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (object_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}') order by 2 desc;
type t_result is table of xp_import_mw%rowtype;
v_result t_result;
v_sql varchar2(300);
BEGIN
for i in c_tables
loop
v_sql := 'select * from ' || i.object_name || ' ' || pi_where;
execute immediate v_sql bulk collect into v_result;
if v_result.count > 0
then
for j in v_result.first .. v_result.last
loop
dbms_output.put_line(v_result(j).art_nr);
end loop;
dbms_output.put_line('... the required information was found on table name ' || upper(i.object_name));
exit;
end if;
end loop;
END prc_checks;
You'll get this is one of the tables being found by the cursor has fewer columns than xp_import_mw. For example:
create table xp_import_mw (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160102 (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160101 (col1 number, art_nr number);
insert into xp_import_mw_arch_20160101 values (1, 42);
So the main xp_import_mw table has three columns but no matching data. One of the old archive tables has one fewer columns.
I added a dbms_output.put_line(v_sql) to the procedure to see which table it fails against, then ran it:
set serveroutput on
exec prc_checks('where col1 = 1');
which got output:
select * from XP_IMPORT_MW where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160102 where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160101 where col1 = 1
Error starting at line : 49 in command -
BEGIN prc_checks('where col1 = 1'); END;
Error report -
ORA-01007: variable not in select list
ORA-06512: at "MY_SCHEMA.PRC_CHECKS", line 25
ORA-06512: at line 1
01007. 00000 - "variable not in select list"
*Cause:
*Action:
So the problem isn't that there is no data found; the problem is that there is matching data in a table which has the wrong structure.
You could construct the select list based on the xp_import_mw table's structure, instead of using *; that won't stop it failing, but would at least give you a slightly more helpful error message - in this case ORA-00904: "DUMMY": invalid identifier instead of ORA-01007.
You could do a quick and crude check for discrepancies with something like:
select table_name, count(column_id) as column_count,
listagg(column_name, ',') within group (order by column_id) as columns
from dba_tab_columns
where table_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (table_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}')
group by table_name
having count(column_id) != (
select count(column_id) from dba_tab_columns where table_name = 'XP_IMPORT_MW'
);
... although if you're using dba_* or all_* view you should really be including the owner, here and in your procedure.

Create a table in Oracle SQL If table does not exist using select and join

The question is very common, but I am facing one single issue in there for which I am not able to find the answer.
What I have to do is, create a table if the table does not exist.
But while creating the table, I need to create it from the select query(which is result of JOIN of two tables).
This is for "ORACLE SQL DEVELOPER" / "PL/SQL".
The query I am using is:
DECLARE
count_matching_tbl BINARY_INTEGER := 0;
BEGIN
SELECT COUNT(*)
INTO count_matching_tbl
FROM dba_tables
WHERE LOWER(table_name) = 'testtable';
IF(count_matching_tbl = 0)
THEN
EXECUTE IMMEDIATE ( ' CREATE TABLE testtable AS (SELECT A.*, B.* from tab1 A JOIN tab2 B ON A.id = B.RID WHERE 1=2)');
END IF;
END;
If tab1 and tab2 table has same name column then ambiguity occure while creating table and inserting record so instead of * replace table column which is add in testtable like
CREATE TABLE testtable AS (SELECT A.cola1, B.colb1 from tab1 A JOIN tab2 B ON A.id = B.RID WHERE 1=2
ask your dba to give "select on dba_tables " to your schema .
Since you are using a pl/sql procedure , you need to have permissions granted directly to you rather than through a role.
The issue was because of the static SQL data.
The table was created dynamically, but the rest of the statements were trying to access the static data.
To solve this, I have created two anonymous blocks, the first one having the create table statement, commit it and end the block.
The next anonymous block will have the rest of the statements to be executed after the IF clause.
This solved my problem.
DECLARE
count_matching_tbl BINARY_INTEGER := 0;
BEGIN
SELECT COUNT(*)
INTO count_matching_tbl
FROM dba_tables
WHERE LOWER(table_name) = 'testtable';
IF(count_matching_tbl = 0)
THEN
EXECUTE IMMEDIATE ( ' CREATE TABLE testtable AS (SELECT A.*, B.* from tab1 A JOIN tab2 B ON A.id = B.RID WHERE 1=2)');
COMMIT;
END IF;
END;
/
BEGIN
-- Rest of the database execution statements
COMMIT;
END;
/

oracle query select a column (where column name is unknown)

My column name is retrieved from another query, then using that result as the column name I want to query to get the value. I have found a duplicate question here but I could not find the answer. So my code looks like this:
DECLARE
COL_NO NUMBER(3,0);
COL_NAME VARCHAR2(30);
COL_VALUE VARCHAR2(100);
BEGIN
COL_NO:=0;
COL_NAME:=NULL;
COL_VALUE:=NULL;
SELECT COUNT(*) INTO COL_NO FROM user_tab_columns WHERE table_name='SALARYSLIP';
FOR A IN 4..COL_NO LOOP
SELECT column_name INTO COL_NAME FROM user_tab_columns WHERE column_id=A AND table_name='SALARYSLIP';
--SELECT COL_NAME INTO COL_VALUE FROM SALARYSLIP WHERE ID=SALARYSLIP.ID;
DBMS_OUTPUT.PUT_LINE(COL_VALUE);
END LOOP;
END;
The commented query is supposed to fetch the column value but is not working obviously. What should I write there? TIA.
I'm not exactly sure what you are trying to accomplish. Counting the umber of columns in the table and iterating from 4 to that number doesn't seem particularly logical. In the desired SQL statement, it's also not clear where you are getting the value for the id that you want to use.
If you want to determine the column name at runtime, then you would need to use dynamic SQL. Something like
FOR A IN 4..COL_NO LOOP
SELECT column_name INTO COL_NAME FROM user_tab_columns WHERE column_id=A AND table_name='SALARYSLIP';
--SELECT COL_NAME INTO COL_VALUE FROM SALARYSLIP WHERE ID=SALARYSLIP.ID;
EXECUTE IMMEDIATE 'SELECT ' || col_name ||
' FROM salaryslip ' ||
' WHERE id = :1'
INTO col_value
USING <<whatever ID value you want>>;
DBMS_OUTPUT.PUT_LINE(COL_VALUE);
END LOOP;
For simplicity, I'm not using a local variable for the SQL statement that is assembled before being dynamically executed. Normally, you would want to have a local variable with the SQL statement so that you can do things like log the statement that was assembled in case it doesn't do what you expected.
I think the logic needs to be addressed first - i.e. whether the required results is in terms of the rows from the SALARYSLIP table, OR the columns of the SALARYSLIP table.
SELECT COUNT(*) INTO COL_NO FROM user_tab_columns WHERE table_name='SALARYSLIP'; ( this would get the number of columns from the SALARYSLIP table )
After this, what is the desired result to iterate through the for loop, based on the number of columns on the SALARYSLIP table? i.e. since, generally, the output is based on the rows being retrieved.
Where is the "ID" value coming from? It's not being initialized / declared in the code.
From what I gather, the column_name of the table is required to be retrieved at runtime. Then, the data for this column should be displayed for the SALARYSLIP table.
Try this :
DECLARE
COL_NO NUMBER(3,0);
COL_NAME VARCHAR2(30);
COL_VALUE VARCHAR2(100);
ROWNO NUMBER(3,0);
BEGIN
COL_NO:=0;
COL_NAME:=NULL;
COL_VALUE:=NULL;
SELECT COUNT(*) INTO COL_NO FROM user_tab_columns WHERE table_name='SALARYSLIP'; --number of columns in SALARYSLIP
SELECT Count(*) INTO rownno FROM SALARYSLIP; --number of rows in SALARYSLIP
--go through each column
FOR A IN 1..COL_NO LOOP
SELECT column_name INTO COL_NAME FROM user_tab_columns WHERE column_id=A AND table_name='SALARYSLIP'; --column_name
--go through each row in the SALARYSLIP table
--for each row, get the specific column of the row, and print this column's value
FOR B IN 1..rowno LOOP
SELECT COL_NAME INTO COL_VALUE FROM SALARYSLIP WHERE =SALARYSLIP.COL_NAME = COL_NAME;
DBMS_OUTPUT.PUT_LINE(COL_VALUE);
END LOOP;
END LOOP;
END;

Dynamically selecting partitions

I have a table with a few hundred partitions and I am generally interested on the latest 35.
Accordingly I am trying to create views which would access these dynamically. i.e. always use the latest in case ones are created.
The query:
select PARTITION_NAME,
PARTITION_POSITION,
NUM_ROWS,
AVG_ROW_LEN
from all_tab_partitions
where
table_name = 'MY_TABLE'
AND PARTITION_NAME <> 'P_LAST'
AND PARTITION_POSITION < (SELECT MAX(PARTITION_POSITION)
FROM all_tab_partitions) - 35
order by 2 DESC
;
Seems to return me the partition names I'm interested, however, I don't manage to use it's results to select the partitions. e.g.:
CREATE OR REPLACE VIEW MY_VIIEW AS
WITH t AS ( [Above query] )
SELECT * FROM
MY_TABLE PARTITION (SELECT /*+ FIRST_ROWS(1) */ PARTITION_NAME
from t);
(not the actual view, just an example)
So how do I do that? How do I create a view which will acess always the latest partition (execpt of "MAX")?
I am using Oracle 10g
thanks
You can do it using PL/SQL only
create or replace package my_table_ is
type t_records is table of my_table%rowtype;
function getpart(c_parts sys_refcursor) return t_records pipelined;
end;
create or replace package body my_table_ is
function getpart(c_parts sys_refcursor) return t_records pipelined is
v_partition all_tab_partitions.partition_name%type;
v_row my_table%rowtype;
c_mytab sys_refcursor;
begin
loop
fetch c_parts into v_partition;
exit when c_parts%notfound;
open c_mytab for 'select * from my_table partition ('||v_partition||')';
loop
fetch c_mytab into v_row;
exit when c_mytab%notfound;
pipe row (v_row);
end loop;
end loop;
end;
end;
Now you can
select * from table(my_table_.getpart(cursor(<QUERY_RETURNING_PARTITION_NAMES>)));
May be you can construct view's query using batch of union all statements with partition name in each statement, e.g.
create view p as
select * from my_table partition (part1)
union all
select * from my_table partition (part1)
...
union all
select * from my_table partition (part35)
Ok... I don't think your can use the Partition-Names, but you can use the Starting-Values of the Partitions to select the Data matching these Partitions...
So you View would look like this:
SELECT * FROM my_table WHERE date_col > get_part_limit( 'my_table', 35 ):
Where date_col is the column you use for partitioning - and get_part_limit is a stored function you write like this:
...
BEGIN
SELECT high_value FROM all_tab_partitions
INTO local_var
WHERE table_name = parameter_name
AND PARTITION_POSITION = MAX... - 35
EXECUTE IMMEDIATE 'SELECT '||local_var||' FROM DUAL' INTO local_return_value;
RETURN local_return_value;
END;
partitions are designed to be transparent for the data, so when you write a query, you simply don't know how your data is stored.
I see only one possibility to hit a particular partition: your WHERE clause should match values to the partitioned columns of latest (or latest 5) partition.
Next question is to build this WHERE clause on the fly. You already know that there is plenty of information in oracle dictionary. So you will read that and create a constructor to convert metadata conditions back into SQL.
irl we do exactly the same thing and use falco's solution like.
Here is my code:
create or replace function longToDate( myOwner varchar2,
mytable_name in varchar2,
mypartition_name in varchar2
) return date
as
cDate date;
cvar varchar2(1024);
rq varchar2(1024);
infiniteValue EXCEPTION;
PRAGMA EXCEPTION_INIT(infiniteValue, -00904);
begin
select high_value into cvar FROM dba_tab_partitions t where t.table_owner=myOwner and table_name=mytable_name and partition_name=mypartition_name;
rq:='select '||cvar||' from dual';
execute immediate rq into cDate;
return cdate;
EXCEPTION
WHEN infiniteValue
then return'01 jan 3000';
when others
then return null;
end longToDate;
Ant the view is something like this
create or replace view last_35 as
with maxdate as
(select longToDate(p.table_owner,p.table_name,p.partition_name) mydate,
rank()over(order by p.partition_position desc) mypos,
p.* from all_tab_partitions p
where p.table_name='MY_TABLE'
)
select /*+full(a)*/* from MY_TABLE a, maxdate
where MY_TABLE.partition_name>maxdate.mydate
and maxdate.mypos=35

Resources