CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
P_REC.EMPID ||
',' ||
P_REC.MGRID ||
',' ||
P_REC.EMPNAME ||
',' ||
P_REC.SALARY ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
When I run this:
select SP_NEW_PROCEDURE1()
I get the errors:
ERROR [01000] NOTICE: Error occurred while executing PL/pgSQL function SP_NEW_PROCEDURE1
ERROR [01000] NOTICE: line 24 at execute statement
ERROR [42S22] ERROR: Attribute 'DAN' not found
Can someone help whats wrong ...thanks
This has nothing do with the cursor itself, and everything to do with how you are building your dynamical SQL string.
When building dynamic SQL in a Netezza stored procedure, you can use the quote_ident and quote_literal helper functions to let the system know whether you are passing it a literal, or whether you are passing it an identifier. There is an example in the online documentation here. Essentially all they do is figure out the escaped quotation notation needed.
Since you are trying to put the values stored in the columns of your P_REC record into the VALUES part of an insert statement, you could use quote_literal like this:
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' VALUES (' ||
quote_literal(P_REC.EMPID) ||
',' ||
quote_literal(P_REC.MGRID) ||
',' ||
quote_literal(P_REC.EMPNAME) ||
',' ||
quote_literal(P_REC.SALARY ) ||
' ) ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
END LOOP;
RETURN REFTABLE;
END;
END_PROC;
That being said, using a cursor to loop over records to insert a row one at a time is horribly inefficient in an MPP database like Netezza. Assuming this question is a follow-on from your question about an alternative to a recursive CTE to explore hierarchies, there's nothing wrong with looping in general, but try to avoid doing it record by record. Here is a version that will exploit the MPP nature of the system. For the record, if you are going to return your result set to a REFTABLE, then your only choice is Dynamic SQL.
CREATE OR REPLACE PROCEDURE SP_NEW_PROCEDURE1( )
RETURNS REFTABLE(employees)
LANGUAGE NZPLSQL AS
BEGIN_PROC
DECLARE
l_conditions varchar(1000);
p_rec RECORD;
BEGIN
-- FOR P_REC IN select empid, mgrid, empname, salary from employees where mgrid = 7
-- LOOP
l_conditions := 'insert into ' ||
REFTABLENAME ||
' select empid, mgrid, empname, salary from employees where mgrid = 7 ; ' ;
execute immediate l_conditions;
l_conditions := ' ';
-- END LOOP;
RETURN REFTABLE;
END;
END_PROC;
I suspect that you are building a query that is meant to insert a literal 'DAN' but which does not include the required quote marks, hence it is referencing DAN -- the optimiser is therefore trying to find an attribute of that name.
So the fix is to include the quotation marks when you build the SQL insert statement, or (preferably) to just use static SQL to insert the values instead of execute immediate.
When in doubt, always look at the data, as this would probably have been obvious to you if you inspected the value of l_conditions.
Related
I'm an Oracle/PL/SQL Developer newbie, and I'm struggling to figure out how to see the output of this query:
DECLARE
ncount NUMBER;
vwhere VARCHAR2(1000) := '';
vselect VARCHAR2(1000) := ' select count(1) from ';
vsearchstr VARCHAR2(1000) := '1301 250 Sage Valley Road NW';
vline VARCHAR2(1000) := '';
istatus INTEGER;
BEGIN
DBMS_OUTPUT.ENABLE;
FOR k IN (SELECT a.table_name, a.column_name FROM user_tab_cols a WHERE a.data_type LIKE '%VARCHAR%')
LOOP
vwhere := ' where ' || k.column_name || ' = :vsearchstr ';
EXECUTE IMMEDIATE vselect || k.table_name || vwhere
INTO ncount
USING vsearchstr;
IF (ncount > 0)
THEN
dbms_output.put_line(k.column_name || ' ' || k.table_name);
ELSE
dbms_output.put_line('no output');
END IF;
END LOOP;
dbms_output.get_line(vline, istatus);
END;
I got this script from https://community.oracle.com/tech/developers/discussion/2572717/how-to-search-a-particular-string-in-whole-schema. It's supposed to find a string (vsearchstr) in the entire database. When I run this in PL/SQL Developer 14.0.6, it spits out no errors, says it took 0.172 seconds, but I don't see any output. I'm expecting the output to show under the Output tab:
I know the string '1301 250 Sage Valley Road NW' exists in the database so it should be finding it. Even if it doesn't, the ELSE block should be outputting 'no output'.
From what I understand, dbms_output.put_line() adds the given string to a buffer, and dbms_output.get_line() prints it to the output target (whatever it's set to). I understand that dbms_output needs to be enabled (hence the line DBMS_OUTPUT.ENABLE) and dbms_output.get_line() will only run after the BEGIN/END block it's in completes (I don't know if this means it has to be put outside the BEGIN/END block, but I couldn't avoid certain errors every time I did).
I've read through various stackoverflow posts about this issue, as well as a few external site:
https://docs.oracle.com/cd/F49540_01/DOC/server.815/a68001/dbms_out.htm#1000449
https://www.tutorialspoint.com/plsql/plsql_dbms_output.htm
...but nothing seems to be working.
How can I see the output, or if there's something wrong in the query above, can you tell what it is?
Thanks.
To enable output from DBMS_OUTPUT in PL/SQL Developer see this answer.
I'm looking for an alternative keyword to user_tab_cols for all schemas in the DB
Use ALL_TAB_COLS and catch the exceptions when you do not have enough privileges to read the table (and use quoted identifiers to match the case of user/table/column names):
DECLARE
found_row PLS_INTEGER;
vsearchstr VARCHAR2(1000) := '1301 250 Sage Valley Road NW';
BEGIN
FOR k IN (SELECT owner,
table_name,
column_name
FROM all_tab_cols t
WHERE data_type LIKE '%VARCHAR%'
-- Ignore columns that are too small
AND data_length >= LENGTH(vsearchstr)
-- Ignore all oracle maintained tables
-- Not supported on earlier Oracle versions
AND NOT EXISTS (
SELECT 1
FROM all_users u
WHERE t.owner = u.username
AND u.oracle_maintained = 'Y'
)
)
LOOP
DECLARE
invalid_privileges EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_privileges, -1031);
BEGIN
EXECUTE IMMEDIATE 'SELECT 1 FROM "' || k.owner || '"."' || k.table_name || '" WHERE "' || k.column_name || '" = :1 AND ROWNUM = 1'
INTO found_row
USING vsearchstr;
dbms_output.put_line('Found: ' || k.table_name || '.' || k.column_name);
EXCEPTION
WHEN invalid_privileges THEN
NULL;
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('Not found: ' || k.table_name || '.' || k.column_name);
END;
END LOOP;
END;
/
How do I get the data(i.e rows) from the column_name I retrieved from SQL statement? (Completely new to PL/SQL).
Here is my code:
create or replace procedure com_coll_cur
as
type comColcur is ref cursor;
com_col_cur comColcur;
sql_stmt varchar2(4000);
type newtab_field is table of comColcur%TYPE;
begin
sql_stmt :=
'select column_name from all_tab_cols where table_name in (''TAB1'', ''TAB2'') ' ||
'group by column_name having count(*)>1';
open com_col_cur for sql_stmt;
loop
fetch com_col_cur into newtab_field;
exit when com_col_cur%NOTFOUND;
end loop;
close com_col_cur;
end;
What I'm trying to do here is first find the common columns between the two tables. This part only grabs column_name but I also want the data in that common columns. So I used cursor to "point" that common column_name and used loop(fetch) to get the data inside that common column_name. Finally, I want to create new table with this common columns only with their data.
I am new to everything here and any help will be appreciate it.
You don't understand how works cursors and fetch.
Fetch get the data from the cursor, so in your procedure example you get only column names, not the data in the columns. To get data you need another cursor - select from the target table or use the dynamic sql.
This is a code that do what you describe. It is not clear to me how you want to store data from two tables - subsequently or in another manner. Let's assume that we store them subsequently. Also this code suggests than columns with the same names have the same datatypes. Part of this code (to make datatype) I get from another stackoverflow post to save time to write it:
How do I get column datatype in Oracle with PL-SQL with low privileges?
dbms_output.put_line - used to print sql statements that we create
declare
cSql varchar2(4000);
cCols varchar2(4000);
cNewTableName varchar2(30) := 'AABBCC';
cTb1 varchar2(30) := 'TAB1';
cTb2 varchar2(30) := 'TAB2';
begin
for hc in (
select T.column_name, T.typ
from
(
select column_name,
data_type||
case when data_precision is not null and nvl(data_scale,0)>0 then '('||data_precision||','||data_scale||')'
when data_precision is not null and nvl(data_scale,0)=0 then '('||data_precision||')'
when data_precision is null and data_scale is not null then '(*,'||data_scale||')'
when char_length>0 then '('||char_length|| case char_used
when 'B' then ' Byte'
when 'C' then ' Char'
else null
end||')'
end||decode(nullable, 'N', ' NOT NULL') typ
from all_tab_cols
where table_name in (cTb1, cTb2) ) T
group by T.column_name, T.typ having count(*) > 1)
loop
cSql := cSql || case when cSql is null then null else ',' end || hc.column_name || ' ' || hc.typ;
cCols := cCols || case when cCols is null then null else ',' end || hc.column_name;
end loop;
if (cSql is not null) then
-- First drop table if it exists
for hc in (select * from all_objects where object_type = 'TABLE' and object_name = cNewTableName)
loop
execute immediate 'drop table ' || hc.object_name;
end loop;
-- create table
cSql := 'create table ' || cNewTableName || '(' || cSql || ')';
dbms_output.put_line(cSql);
execute immediate cSql;
-- insert data
cSql := 'insert into ' || cNewTableName || '(' || cCols || ') select ' || cCols || ' from ' || cTb1;
dbms_output.put_line(cSql);
execute immediate cSql;
cSql := 'insert into ' || cNewTableName || '(' || cCols || ') select ' || cCols || ' from ' || cTb2;
dbms_output.put_line (cSql);
execute immediate cSql;
end if;
end;
I'm sorry upfront because this question seems to easy.
I have this function:
CREATE OR REPLACE FUNCTION Costs_MK (VIEWNAME IN VARCHAR2 , WHERE_CLAUSE IN VARCHAR2)
RETURN VARCHAR2
IS
v_Costs VARCHAR2 (500);
BEGIN
Select Listagg(Costs, ';' ) WITHIN GROUP (ORDER BY Costs)
into v_Costs
from (select distinct (Costs)
from VIEWNAME
where WHERE_CLAUSE);
RETURN v_Costs;
END Costs_MK;
However I get the Error-Message:
Error(13,30): PL/SQL: ORA-00920: invalid relational operator
I even can't compile it. If I use the exact values for Viewname and Where_clause I get the desired result.
What am I doing wrong?
/edit: Line 13 is
from VIEWNAME
/edit #2:
Thanks guys. You helped me a lot. I didn't thought about dynamic sql in the first step, so thanks for the refresher ;).
I suggest you to add EXCEPTION BLOCK along with EXECUTE IMMEDIATE
I have created a PROCEDURE you can similary create FUNCTION
CREATE OR REPLACE procedure Costs_PK(VIEWNAME IN VARCHAR2 , WHERE_CLAUSE IN VARCHAR2 )
AS
v_Costs VARCHAR2 (500);
sql_stmnt varchar2(2000);
BEGIN
sql_stmnt := 'Select Listagg(Cost, '';'' ) WITHIN GROUP (ORDER BY Cost) from (select distinct (Cost) from ' || VIEWNAME || ' where ' || WHERE_CLAUSE || ' ) ';
--sql_stmnt := 'Select Listagg(Cost, '';'' ) WITHIN GROUP (ORDER BY Cost) from (select distinct (Cost) from cost_tab where cost >=123 ) ';
EXECUTE IMMEDIATE sql_stmnt INTO v_Costs ;
dbms_output.put_line ('query works -- ' || v_costs);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('input :' || VIEWNAME || ' and ' || WHERE_CLAUSE );
dbms_output.put_line (sql_stmnt );
DBMS_OUTPUT.PUT_LINE ('ERROR MESSAGE : ' || sqlCODE || ' ' || SQLERRM );
END;
begin
Costs_PK('cost_tab','cost >= 123');
end;
NOTE: code has been Tested
output:
query works -- 123;456
This is one of the areas in PL/SQL where the most straightforward static SQL solution requires code duplication as there is no way to parametrize the table name in a query. Personally I usually favor duplicate code of static SQL over the increased complexity of dynamic SQL as I like PL/SQL compiler to check my SQL compile time. YMMV.
You don't tell us what kind of where statements the different views are having. In the example below I assume there is 1:1 relation between the view and where parameter(s) so I can easily build static SQL.
create or replace view foo_v (foo_id, cost) as
select level, level*10 from dual connect by level < 10
;
create or replace view bar_v (bar_id, cost) as
select level, level*100 from dual connect by level < 10
;
create or replace function cost_mk(
p_view in varchar2
,p_foo_id in number default null
,p_bar_id in number default null
) return varchar2 is
v_cost varchar2(32767);
begin
case lower(p_view)
when 'foo_v' then
select listagg(cost, ';' ) within group (order by cost)
into v_cost
from (select distinct cost
from foo_v
where foo_id < p_foo_id);
when 'bar_v' then
select listagg(cost, ';' ) within group (order by cost)
into v_cost
from (select distinct cost
from bar_v
where bar_id < p_bar_id);
end case;
return v_cost;
end;
/
show errors
Usage example
select cost_mk(p_view => 'foo_v', p_foo_id => 5) from dual;
select cost_mk(p_view => 'bar_v', p_bar_id => 5) from dual;
You would want to use EXECUTE IMMEDIATE as was hinted by the comments to your question, define a new variable sqlQuery VARCHAR2(200); or similar and rework your sql similar to the following:
sqlQuery := 'Select Listagg(Costs, '';'' ) WITHIN GROUP (ORDER BY Costs) ';
sqlQuery := sqlQuery || 'from (select distinct (Costs) from :1 where :2)';
EXECUTE IMMEDIATE sqlQuery INTO v_Costs USING VIEWNAME, WHERE_CLAUSE;
I have a table attribute_config with below columns:
table_name column_name key
Let us say is has below 2 rows
account accountphone accountnum
customer customernumber customerid
Key can be only accountnum or customerid.
I have to write code which will accept (i_accountnum,i_customerid) and;
fetch the respective values from columns mentioned in column_name in tables mentioned in table_name using the key in where condition.
For ex: select accountphone from account where accountnum = i_accountnum
select customernumber from customer where customerid = i_customerid
the complete query should be formed dynamically, whether to pass i_accountnum or i_customerid in the query also needs to be decided dynamically. if key - accountnum, i_accountnum will be passed to where condition.
I have been trying on these lines so far, this is not working, i know it is wrong.
declare
v_accountnum varchar2(20);
v_customerid varchar2(20);
v_attribute_value varchar2(20);
v_stmt varchar2(255);
begin
Account_Num := 'TestCustomer'; -- input to the function
v_customer_ref := 'TestAccount'; -- input to the function
for i in (Select * from attribute_config) loop
v_stmt := 'select ' || i.column_name || ' from ' || i.table_name ||' where ' || i.key|| ' = v_' || i.key;
execute immediate v_Stmt into v_attribute_value;
end loop;
end;
This will fix your code, but I do not see any advantage of using dynamic query when your code should accept 2 parameters(i_accountnum,i_customerid) - which is already static situation and fetch the relevant values, perhaps only in learning purposes.
declare
procedure fecth_values(i_accountnum account.accountnum%type,
i_customerid customer.customerid%type) return varchar2 is
v_attribute_value varchar2(20);
begin
for i in (select * from attribute_config) loop
execute immediate 'select ' || i.column_name || ' from ' ||
i.table_name || ' where ' || i.key || ' = ' || case when i.key = 'accountnum' then i_accountnum when i.key = 'customerid' then i_customerid end;
into v_attribute_value;
dbms_output.put_line(v_attribute_value);
end loop;
return null;
end;
begin
fecth_values(1, 1);
end;
Your where clause was wrong the i.key should be compared against the inputed values, not the 'v_' || i.key, which is undeclared when you execute your stmt.
The following function generates the SELECT to produce INSERT STATEMENT of the records of an Oracle table:
CREATE OR REPLACE FUNCTION GEN_INSERT_STATEMENT (IN_TABLE_NAME VARCHAR2)
RETURN CLOB
IS
LC$COLS_SELECT CLOB;
LC$COLS_VALUES CLOB;
LC$COLOUMN CLOB;
CURSOR LCUR$TAB_COLUMNS (IN_TABLE_NAME VARCHAR2)
IS
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_ID
FROM USER_TAB_COLS
WHERE TABLE_NAME = IN_TABLE_NAME
ORDER BY COLUMN_ID;
BEGIN
FOR LREC$TAB_COLUMNS IN LCUR$TAB_COLUMNS (UPPER (IN_TABLE_NAME))
LOOP
LC$COLS_SELECT :=
LC$COLS_SELECT
|| CASE LREC$TAB_COLUMNS.COLUMN_ID WHEN 1 THEN '' ELSE ',' END
|| LREC$TAB_COLUMNS.COLUMN_NAME;
IF INSTR (LREC$TAB_COLUMNS.DATA_TYPE, 'CHAR') > 0
THEN
LC$COLOUMN :=
'''''''''||REPLACE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ','''''''','''''''''''')||''''''''';
ELSIF INSTR (LREC$TAB_COLUMNS.DATA_TYPE, 'DATE') > 0
THEN
LC$COLOUMN :=
'''TO_DATE(''''''||TO_CHAR(' || LREC$TAB_COLUMNS.COLUMN_NAME
|| ',''mm/dd/yyyy hh24:mi:ss'')||'''''',''''mm/dd/yyyy hh24:mi:ss'''')''';
ELSE
LC$COLOUMN := LREC$TAB_COLUMNS.COLUMN_NAME;
END IF;
LC$COLS_VALUES :=
LC$COLS_VALUES
|| CASE LREC$TAB_COLUMNS.COLUMN_ID WHEN 1 THEN '' ELSE ',' END
|| '''||DECODE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ',NULL,''NULL'','
|| LC$COLOUMN
|| ')||''';
END LOOP;
RETURN 'SELECT ''INSERT INTO '
|| IN_TABLE_NAME
|| ' ('
|| LC$COLS_SELECT
|| ') VALUES ('
|| LC$COLS_VALUES
|| ');'' FROM '
|| IN_TABLE_NAME
|| ';';
END;
/
The problem is that this function DOES NOT HANDLE the case in which are existing some VARCHAR2 fields with strings ending with: CHR(0)
Usage:
SELECT GEN_INSERT_STATEMENT ('MY_ORACLE_TABLE_NAME') FROM DUAL;
... generates a SELECT to produce the INSERT statement.
If in a VARCHAR2 field the value ends with CHR(0), the INSERT statement will be truncated exactly where is CHR(0) located.
How can I fix this?
If I understand what you're seeing, you need to either strip the null character from the value:
IF INSTR (LREC$TAB_COLUMNS.DATA_TYPE, 'CHAR') > 0
THEN
LC$COLOUMN :=
'''''''''||REPLACE(REPLACE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ',CHR(0),NULL),'''''''','''''''''''')||''''''''';
which gives your function the output:
SELECT 'INSERT INTO T42 (STR) VALUES ('||DECODE(STR,NULL,'NULL',''''||REPLACE(REPLACE(STR,CHR(0),NULL),'''','''''')||'''')||');' FROM T42;
and with a dummy table called t42 with a single column str containing 'hello' followed by a null character, the output is:
INSERT INTO T42 (STR) VALUES ('Hello');
Or preserve it during the insert:
LC$COLOUMN :=
'''''''''||REPLACE(REPLACE('
|| LREC$TAB_COLUMNS.COLUMN_NAME
|| ','''''''',''''''''''''),CHR(0),''''''||CHR(0)||'''''')||''''''''';
which gives:
SELECT 'INSERT INTO T42 (STR) VALUES ('||DECODE(STR,NULL,'NULL',''''||REPLACE(REPLACE(STR,'''',''''''),CHR(0),'''||CHR(0)||''')||'''')||');' FROM T42;
and finally:
INSERT INTO T42 (STR) VALUES ('Hello'||CHR(0)||'');
So in the second version the null character is removed from the fixed string (anywhere, not just at the end), and is put back as part of the insert statement.
This seems like a horrible approach though, when you could export the data, or let SQL Developer or some other IDE generate the insert statements for you. There maybe other data types and values that give you headaches, and which someone else has already worked hard to overcome. Unless you really need to be able to see the actual statements, using expdp would be far simpler.
Use this:
https://github.com/teopost/oracle-scripts/blob/master/fn_gen_inserts.sql
Usage:
select fn_gen_inserts('select * from tablename', 'p_new_owner_name', 'p_new_table_name')
from dual;
where:
p_sql – dynamic query which will be used to export metadata rows
p_new_owner_name – owner name which will be used for generated INSERT
p_new_table_name – table name which will be used for generated INSERT
You can find original source code here:
http://dbaora.com/oracle-generate-rows-as-insert-statements-from-table-view-using-plsql/