When try compaile my packages get error.
PLS-00306: wrong number or types of arguments in call to '||'
v_sql := 'select count(*) from table ('|| p_month_ids ||') where column_value = ' || p_month_id;
Execute Immediate v_sql into v_count;
You are trying to concatenate values into a string, but that can only work if each individual value is either a string or can be implicitly converted to a string. If p_month_ids is a collection - as it seems to be from how you're using it - then you would have to add each element individually - as you can't implicitly convert the p_month_ids collection to a string.
But don't do that; use bind variables:
v_sql := 'select count(*) from table (:p_month_ids) where column_value = :p_month_id';
execute immediate v_sql into v_count using p_month_ids, p_month_id;
Or don't use dynamic SQL at all:
select count(*) into v_count
from table (p_month_ids) where column_value = p_month_id;
Or loop over the collection and count matches directly, instead of using a query (and context switch).
db<>fiddle
Related
I have a procedure which I'm using to output row counts to a .csv file but some of the where clauses I may want to use are contained in a table. How can I use them to create conditions for the counts?
I've attempted using concatenation pipes to select against the table that holds the where clauses but I'm confused about syntax and where they should go and I believe this is where I need the most help.
These are the columns in the table that contains some of the where clauses I ultimately want to use in the procedure.
SCHEMA, DATABASE, FULL_TABLE, DRIVER_TABLE, MAND_JOIN
And the values may be such as:
PROD, DB1, RLTSHP, BOB.R_ID, A.AR_ID = B.AR_ID
The procedure I have written is as follows:
create or replace procedure PROJECT is
--variables
l_dblink varchar2(100) := 'DB1';
ROW_COUNT number;
file_handle UTL_FILE.file_type;
BEGIN
utl_file.put_line(file_handle, 'OWNER,TABLE_NAME,ROW_COUNT');
--main loop
for rws in (select /*+parallel */ owner, table_name
from dba_tables#DB1 a
where table_name in (select table_name
from meta_table
where driver_table is not null
and additional_joins is null)
and a.owner in (select distinct schema
from meta_table c)
order by table_name)
loop
execute immediate 'select count(*) from ' ||rws.owner||'.'||rws.table_name || '#' || l_dblink into ROW_COUNT;
utl_file.put_line(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
ROW_COUNT);
end loop;
END PROJECT;
/
However, instead of the simple select count(*) reflected in the above, I want to find a way to include data in the meta_table to construct "where" clauses that use table joins to limit the output so that I'm not counting all rows, but rows that meet the criteria in the join I've constructed.
For example, so that the actual count that gets executed will be something like this:
select count(*)
from PROD.RLTSHP#DB1 b,
BOB.R_ID#DB1 a
where A.AR_ID = B.AR_ID;
Essentially I would be constructing the query using the entries in the meta_table. I think I can do this with concat's / pipes but I'm not sure exactly how to.
Can you help?
You need to extend your simple statement to assemble the join criteria as well. The one catch is that you must give the tables aliases which match the aliases used in additional_joins i.e. B for FULL and A for DRIVER. These have to be standard for all rows in your META_TABLE otherwise you will generate invalid SQL.
create or replace procedure PROJECT is
l_dblink varchar2(100) := 'DB1';
ROW_COUNT number;
file_handle UTL_FILE.file_type;
v_sql varchar2(32767);
BEGIN
utl_file.put_line(file_handle, 'OWNER,TABLE_NAME,ROW_COUNT');
<< main_loop >>
for rws in (select mt.*
from dba_tables#DB1 db
join meta_table mt
on mt.driver_table = db.table_name
and mt.owner = db.owner
where mt.db_link = l_dblink
order by mt.table_name)
loop
-- simple query
v_sql := 'select count(*) from ' || rws.owner||'.'||rws.driver_table || '#' || l_dblink;
-- join query
if rws.additional_joins is not null
and rws.full_table is not null then
v_sql := v_sql|| ' b, '|| rws.full_table ||'#'||l_dblink|| ' a where ' ||rws.additional_joins;
end if;
-- uncomment this for debugging
--dbms_output.put_line(v_sql);
execute immediate v_sql into ROW_COUNT;
utl_file.put(file_handle,
rws.OWNER || ',' ||
rws.TABLE_NAME || ',' ||
utl_file.put_line(file_handle, ROW_COUNT);
end loop main_loop;
END PROJECT;
/
Notes
We have to use a variable to assemble the statement because the final SQL is conditional on the contents of a row. This enables efficient debugging because we have something we can display. Dynamic SQL is hard, because it turns compilation errors into runtime errors. Diagnosis is difficult when we can't see the actual executed code.
I have tweaked your driving query to make the joins safer.
The column names you used in the code are not consist with the column names you used for the table structure. So there may be naming bugs which you'll need to fix for yourself.
I have retained the Old Skool implicit join syntax. I was tempted to generate ANSI 92 SQL (inner join ... on) but it's not clear that the additional_joins will contain only join criteria.
Pro tip. Instead of commenting your loops - --main loop - use an actual PL/SQL label - <<main_loop>> so you can link the matching end loop statement, as I have done in this code.
Improvements you may want to add:
validate that FULL_TABLE exists in target database
include FULL_TABLE in UTL_FILE output
validate that columns referenced in ADDITIONAL_JOIN are valid (using DBA_TAB_COLUMNS, but it's trickier because you will have to parse the column names from the text)
worry about whether the content of ADDITIONAL_JOIN is a valid and complete join condition
First of all I don't recommend to use PARALLEL hint. It can kill your db if you will have a lot of queries with PARALLEL hints.
I assume that columns MAND_JOIN means, that always we have value there.
create or replace procedure PROJECT is
lc_sql_template CONSTANT varchar2(4000) :=
'select count(*) ' || CHR(10) ||
' from #TableOwner.#TableName#DB1 b' || CHR(10) ||
' inner join #FullTableName#DB1 a ON #JoinCodition';
lv_row_count number;
lv_file_handle UTL_FILE.file_type;
lv_sql varchar2(32767);
BEGIN
utl_file.put_line(lv_file_handle, 'OWNER,TABLE_NAME,ROW_COUNT');
for rws in (select mt.*
from dba_tables#DB1 db
inner join meta_table mt
on mt.driver_table = db.table_name
and mt.owner = db.owner
where mt.driver_table is not null
and mt.additional_joins is null
order by mt.table_name)
loop
lv_sql := lc_sql_template;
lv_sql := replace(lv_sql, '#TableOwner' , rws.owner);
lv_sql := replace(lv_sql, '#TableName' , rws.driver_table);
lv_sql := replace(lv_sql, '#FullTableName' , rws.full_table);
lv_sql := replace(lv_sql, '#JoinCodition' , rws.mand_join);
$if $$DevMode = true $then -- I even recommand to log this all the time
your_log_package.info(lv_sql);
$end
execute immediate lv_sql into lv_row_count;
utl_file.put(lv_file_handle, rws.OWNER || ',' || rws.TABLE_NAME || ',' || lv_row_count);
end loop main_loop;
exception
when others then
your_log_package.error(lv_sql);
raise;
end PROJECT;
I have the following query that gives me a result set of all tables and columns in my Oracle database of VARCHAR columns:
SELECT ATC.OWNER, ATC.TABLE_NAME, ATC.COLUMN_NAME
FROM all_tab_columns ATC
WHERE DATA_TYPE LIKE '%VARCHAR%'
To this I want to add a 4th column that displays the value of ATC.COLUMN_NAME. Is there an easy way of doing this?
I thought of doing a join to a SQL statement that loops through ATC.COLUMN_NAME and outputting the value. The join would be done on the table name.
I don't know if I'm complicating it and I can't think of the SQL. I've tried declaring the above statement in a variable and then using a CTE to interrogate it but I would still need to loop through the table_name and column_name values.
Is there a simpler way?
Edit: Sample data
You need to use dynamic SQL. this is a proof of concept, it will not scale well when run against a large database.
declare
stmt varchar2(32767);
val varchar2(4000);
rc sys_refcursor;
begin
for r in ( SELECT ATC.OWNER, ATC.TABLE_NAME, ATC.COLUMN_NAME
FROM all_tab_columns ATC
WHERE DATA_TYPE LIKE '%VARCHAR%' )
loop
stmt := ' select distinct '|| r.column_name ||
' from '|| r.owner||'.'||r.table_name;
open rc for stmt;
loop
fetch rc in val;
exit when rc%notfound;
dbms_output.put_line ( r.owner||'.'||r.table_name ||'.'|| r.column_name
||': '|| val );
end loop;
end loop;
end;
In my stored procedure I use the following query:
SELECT COUNT(*) INTO COL_NO FROM user_tab_columns WHERE table_name='PAY_SLIP_FEB_16';
This query gives no error (because it shouldn't). But the table name here is fixed so I want to use a variable that will provide the table name. So I changed the query like this:
EXECUTE IMMEDIATE 'SELECT COUNT(*) INTO COL_NO FROM user_tab_columns WHERE table_name='''||TBL_NAME||'''';
This query gives error:
ORA-00905: missing keyword
What did I do wrong? TIA.
N.B. : TBL_NAME is the incoming parameter that will provide the table name, and COL_NAME is just a NUMBER(5,0) type variable.
I would expect the code to have the INTO as part of the EXECUTE IMMEDIATE, not in the dynamic query string:
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM user_tab_columns WHERE table_name='''||TBL_NAME||''''
INTO COL_NO ;
As a note: I would write this using a separate variable:
v_sql := 'SELECT COUNT(*) FROM user_tab_columns WHERE table_name = ''#TBL_NAME''';
v_sql := REPLACE(v_sql, '#TBL_NAME', TBL_NAME)
EXECUTE IMMEDIATE v_sql INTO v_COL_NO ;
I have a table A which has column A which holds table names as values.
All these tables have a common column C. I need maximum value of this column for each table.
I tried this using dynamic SQL but I'm getting errors. Please suggest.
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
Cursor cursor_a IS
SELECT a FROM A;
BEGIN
Open cursor_a;
LOOP
Fetch cursor_a INTO c_table2;
EXIT WHEN cursor_a%notfound;
query1 := 'SELECT max(object_ref) AS "c_obj" FROM c_table' ;
EXECUTE IMMEDIATE query1;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
Close cursor_a;
END;
Dynamic SQL can't see your PL/SQL variable: you need to pass it a string which can be executed in the scope of the SQL engine. So you need to concatenate the table name with the statement's boilerplate text:
query1 := 'SELECT max(c) FROM ' || variable_name;
You also need to return the result of the query into a variable.
Here is how it works (I've stripped out some of the unnecessary code from your example):
DECLARE
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
BEGIN
for lrec in ( select a as tab_name from A )
LOOP
EXECUTE IMMEDIATE 'SELECT max(object_ref) FROM ' || lrec.tab_name
into c_obj ;
dbms_output.put_line('Maximum value: '|| lrec.tab_name
|| '='|| c_obj);
END LOOP;
END;
There is some miss match in veriables that you had used i.e.
declared as "c_table" but accessing as "c_table2"
Each table common column name is "C" but accessing as "object_ref"
In dynamic query use INTO keyword to store the value to your varibale
Suggestions
Use concat() function to prepare the query dynamically i.e. something like:
SET #SQL := CONCAT('SELECT max(c) INTO ', c_obj, ' FROM ',c_table);
Steps of implementing dynamic query is:
SET #SQL = <your dynamic query>
PREPARE stmt FROM #SQL;
EXECUTE stmt;
Sample code:
DECLARE
query1 VARCHAR2(100);
c_table VARCHAR2(40);
c_obj VARCHAR2(20);
CURSOR cursor_a IS
SELECT a FROM A;
BEGIN
OPEN cursor_a;
LOOP
FETCH cursor_a INTO c_table;
EXIT WHEN cursor_a%notfound;
SET #SQL := CONCAT('SELECT max(object_ref) AS c_obj INTO ', c_obj, ' FROM ',c_table);
PREPARE stmt FROM #SQL;
EXECUTE stmt;
dbms_output.put_line('Maximum value: '|| c_table || c_obj);
END LOOP;
CLOSE cursor_a;
END;
I want to get count(*) value in dynamic plsql statement.
We can write static stmt as:
select count(*) into tmp_cnt from table_info where nbr_entry='0123456789';
but how to get tmp_cnt value while writing the dynamic sql stament?
or any other way to get count(*) value into tmp_cnt variable?
Maybe different oracle version, but what worked for me was:
...
execute immediate 'select count(*) from ' || p_table_name into l_count;
...
You can achieve it with EXECUTE IMMEDIATE ... RETURNING INTO:
function count_rows(p_table_name varchar2)
return number
is
l_count number;
begin
execute immediate 'select count(*) from ' || p_table_name into l_count;
return l_count;
end count_rows;