Oracle: Choose user-selected column in procedure - oracle

I'm trying to write a procedure that can take a column name as an input parameter and select that column from a table.
I know you can do this in a regular query by simply creating a string variable and referencing the column like such:
DEFINE mycol = 'column1'
SELECT a.&mycol FROM table1 a
This does however not work within a procedure. Variables in procedures don't appear to be able to be referenced by the '&' symbol, and as such when I attempt to pull a column using an input parameter it tells me that it's an invalid identifier.
Searching around on the internet, I can't find an example of someone doing this without dynamic SQL, when inside a proc. I would prefer not to use dynamic SQL if possible.
Does anyone know a workaround for this, or is dynamic SQL required in procs for this to occur?
Cheers,
Ollie

Dynamic SQL in named block is inevitable as ampersand construct are not compliant with SQL standard. It's only feature of SQL Plus engine.

I have tested the scenarios as described.
CREATE TABLE SCOTT.COLUMN_LIST
(
COLUMN_ID NUMBER,
COLUMN_NAME VARCHAR2(200),
TABLE_NAME VARCHAR2(50 )
);
Insert into SCOTT.COLUMN_LIST
(COLUMN_ID, COLUMN_NAME, TABLE_NAME)
Values
(1, 'DEPTNO', 'DEPT');
COMMIT;
CREATE TABLE SCOTT.TEST1
(
A VARCHAR2(300 BYTE)
);
CREATE OR REPLACE PROCEDURE PROC1
IS
v_Select_Column VARCHAR2(200);
v_Table_Name VARCHAR2(50 );
V_SQL VARCHAR2(300);
BEGIN
SELECT COLUMN_NAME,TABLE_NAME
INTO v_Select_Column,v_Table_Name
FROM COLUMN_LIST
WHERE COLUMN_ID=1;
V_SQL:= 'SELECT '||v_Select_Column||
' FROM '||v_Table_Name;
EXECUTE IMMEDIATE V_SQL;
INSERT INTO TEST1 values(V_SQL);
COMMIT;
END PROC1;
/
Please test at your end. You can see the output on test1 table.

Related

ORA-01406 when copying data from one Oracle database to another with different Unicode

I have two identical tables: original_table, destination table in two different Oracle database.
-- Oracle 1
create table original_table
(
my_id NUMBER(11) not null,
my_fld CHAR(15),
)
-- Oracle 2
create table destination_table
(
my_id NUMBER(11) not null,
my_fld CHAR(15),
)
I'm copying data from original_table to destination_table using the procedure and a database link. Here is a pseudocode version.
PROCEDURE COPY_DATA AS
BEGIN
FOR c_cursor IN (SELECT my_id ,my_fld FROM original_table#dblink)
LOOP
INSERT INTO destination_table
VALUES (c_cursor.my_id, c_cursor.my_fld);
END LOOP;
END;
Sometimes Oracle throws ERROR, when special character is inserted in original_table.my_fld column.
ORA-01406: fetched column value was truncated
This is because those two databases have different Unicode and I'm selecting data in LOOP. I tried to write select-insert statement outside of LOOP and it worked fine.
Can you tell me how to fix this problem?
If you just want to copy all data from one table to another u don t need cursor u can do it with sql inside a procedure. Try it hope it helps...
PROCEDURE COPY_DATA AS
BEGIN
INSERT INTO [database].[schema].destination_table (column_list)
SELECT column_list
FROM [database].[schema].original_table
WHERE condition;
END;
Select and insert the data row-by-row is basically the slowest way you can do it. Use this one:
INSERT INTO destination_table (my_id ,my_fld)
SELECT my_id ,my_fld
FROM original_table#dblink;
I used UNISTR function for my string field.
FOR c_cursor IN (SELECT my_id ,UNISTR(my_fld) FROM original_table#dblink)
LOOP
INSERT INTO destination_table
VALUES (c_cursor.my_id, c_cursor.my_fld);
END LOOP;
It fixed the problem.

Insert into not working on plsql in oracle

declare
vquery long;
cursor c1 is
select * from temp_name;
begin
for i in c1
loop
vquery :='INSERT INTO ot.temp_new(id)
select '''||i.id||''' from ot.customers';
dbms_output.put_line(i.id);
end loop;
end;
/
Output of select * from temp_name is :
ID
--------------------------------------------------------------------------------
customer_id
1 row selected.
I have customers table which has customer_id column.I want to insert all the customer_id into temp_new table but it is not being inserted. The PLSQL block executes successfully but the temp_new table is empty.
The output of dbms_output.put_line(i.id); is
customer_id
What is wrong there?
The main problem is that you generate a dynamic statement that you never execute; at some point you need to do:
execute immediate vquery;
But there are other problems. If you output the generated vquery string you'll see it contains:
INSERT INTO ot.temp_new(id)
select 'customer_id' from ot.customers
which means that for every row in customers you'll get one row in temp_new with ID set to the same fixed literal 'customer_id'. It's unlikely that's what you want; if customer_id is a column name from customers then it shouldn't be in single quotes.
As #mathguy suggested, long is not a sensible data type to use; you could use a CLOB but only really need a varchar2 here. So something more like this, where I've also switched to use an implicit cursor:
declare
l_stmt varchar2(4000);
begin
for i in (select id from temp_name)
loop
l_stmt := 'INSERT INTO temp_new(id) select '||i.id||' from customers';
dbms_output.put_line(i.id);
dbms_output.put_line(l_stmt);
execute immediate l_stmt;
end loop;
end;
/
db<>fiddle
The loop doesn't really make sense though; if your temp_name table had multiple rows with different column names, you'd try to insert the corresponding values from those columns in the customers table into multiple rows in temp_new, all in the same id column, as shown in this db<>fiddle.
I guess this is the starting point for something more complicated, but still seems a little odd.

oracle - how to create an object type for an already existing table

We have a requirement where we have to pass the table name to a pl/SQL object at the runtime.
Below is the example
create or replace FUNCTION ABC
(P_TABLE VARCHAR2) RETURN NUMBER IS
C_REFERENCE SYS_REFCURSOR;
V_TABLE VARCHAR2(50):=P_TABLE;
V_C_REF v_table%rowtype;
BEGIN
OPEN C_REFERENCE FOR 'SELECT * FROM '||V_TABLE||;
LOOP
FETCH C_REFERENCE INTO V_C_REF;
EXIT WHEN C_REFERENCE%NOTFOUND;
/*some processing*/
END LOOP;
return(1);
END;
The above code will give me an error. Is there any workaround for it? Table name can vary and different tables will have different structures.
The rowtype declaration, has to be static (if it's not, the compiler can't tell if the fields referenced from it are valid).
Possible solutions are:
Put a comma separated list of variables in the INTO clause:
FETCH C_REFERENCE INTO var1, var2, var3;
Create your own record
TYPE V_C_REF IS RECORD ( col1 VARCHAR2(20), col2 VARCHAR2(25) );
Use a table with the expected estructure
vc_ref table1%ROWTYPE;
write a PL/SQL function to query dba_tab_columns and get the column size and type definition.

FORALL+ EXECUTE IMMEDIATE + INSERT Into tbl SELECT

I have got stuck in below and getting syntax error - Please help.
Basically I am using a collection to store few department ids and then would like to use these department ids as a filter condition while inserting data into emp table in FORALL statement.
Below is sample code:
while compiling this code i am getting error, my requirement is to use INSERT INTO table select * from table and cannot avoid it so please suggest.
create or replace Procedure abc(dblink VARCHAR2)
CURSOR dept_id is select dept_ids from dept;
TYPE nt_dept_detail IS TABLE OF VARCHAR2(25);
l_dept_array nt_dept_detail;
Begin
OPEN dept_id;
FETCH dept_id BULK COLLECT INTO l_dept_array;
IF l_dept_array.COUNT() > 0 THEN
FORALL i IN 1..l_dept_array.COUNT SAVE EXCEPTIONS
EXECUTE IMMEDIATE 'INSERT INTO stg_emp SELECT
Dept,''DEPT_10'' FROM dept_emp'||dblink||' WHERE
dept_id = '||l_dept_array(i)||'';
COMMIT;
END IF;
CLOSE dept_id;
end abc;
Why are you bothering to use cursors, arrays etc in the first place? Why can't you just do a simple insert as select?
Problems with your procedure as listed above:
You don't declare procedures like Procedure abc () - for a standalone procedure, you would do create or replace procedure abc as, or in a package: procedure abc is
You reference a variable called "dblink" that isn't declared anywhere.
You didn't put end abc; at the end of your procedure (I hope that was just a mis-c&p?)
You're effectively doing a simple insert as select, but you're way over-complicating it, plus you're making your code less performant.
You've not listed the column names that you're trying to insert into; if stg_emp has more than two columns or ends up having columns added, your code is going to fail.
Assuming your dblink name isn't known until runtime, then here's something that would do what you're after:
create Procedure abc (dblink in varchar2)
is
begin
execute immediate 'insert into stg_emp select dept, ''DEPT_10'' from dept_emp#'||dblink||
' where dept_id in (select dept_ids from dept)';
commit;
end abc;
/
If, however, you do know the dblink name, then you'd just get rid of the execute immediate and do:
create Procedure abc (dblink in varchar2)
is
begin
insert into stg_emp -- best to list the column names you're inserting into here
select dept, 'DEPT_10'
from dept_emp#dblink
where dept_id in (select dept_ids from dept);
commit;
end abc;
/
There appears te be a lot wrong with this code.
1) why the execute immediate? Is there any explicit requirement for that? No, than don't use it
2) where is the dblink variable declared?
3) as Boneist already stated, why not a simple subselect in the insert statement?
INSERT INTO stg_emp SELECT
Dept,'DEPT_10' FROM dept_emp#dblink WHERE
dept_id in (select dept_ids from dept );
For one, it would make the code actually readable ;)

How do I use CREATE OR REPLACE?

Am I correct in understanding that CREATE OR REPLACE basically means "if the object exists, drop it, then create it either way?"
If so, what am I doing wrong? This works:
CREATE TABLE foo (id NUMBER,
title VARCHAR2(4000) DEFAULT 'Default Title')
And this doesn't (ORA-00922: missing or invalid option):
CREATE OR REPLACE TABLE foo (id NUMBER,
title VARCHAR2(4000) DEFAULT 'Default Title')
Am I doing something stupid? I don't seem to be able to find much documentation about this syntax.
This works on functions, procedures, packages, types, synonyms, trigger and views.
Update:
After updating the post for the third time, I'll reformulate this:
This does not work on tables :)
And yes, there is documentation on this syntax, and there are no REPLACE option for CREATE TABLE.
One of the nice things about the syntax is that you can be sure that a CREATE OR REPLACE will never cause you to lose data (the most you will lose is code, which hopefully you'll have stored in source control somewhere).
The equivalent syntax for tables is ALTER, which means you have to explicitly enumerate the exact changes that are required.
EDIT:
By the way, if you need to do a DROP + CREATE in a script, and you don't care for the spurious "object does not exist" errors (when the DROP doesn't find the table), you can do this:
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE owner.mytable';
EXCEPTION
WHEN OTHERS THEN
IF sqlcode != -0942 THEN RAISE; END IF;
END;
/
There is no create or replace table in Oracle.
You must:
DROP TABLE foo;
CREATE TABLE foo (....);
CREATE OR REPLACE can only be used on functions, procedures, types, views, or packages - it will not work on tables.
Following script should do the trick on Oracle:
BEGIN
EXECUTE IMMEDIATE 'drop TABLE tablename';
EXCEPTION
WHEN OTHERS THEN
IF sqlcode != -0942 THEN RAISE;
END IF;
END;
-- To Create or Replace a Table we must first silently Drop a Table that may not exist
DECLARE
table_not_exist EXCEPTION;
PRAGMA EXCEPTION_INIT (table_not_exist , -00942);
BEGIN
EXECUTE IMMEDIATE('DROP TABLE <SCHEMA>.<TABLE NAME> CASCADE CONSTRAINTS');
EXCEPTION WHEN table_not_exist THEN NULL;
END;
/
Does not work with Tables, only functions etc.
Here is a site with some examples.
A usefull procedure for oracle databases without using exeptions (under circumstances you have to replace user_tables with dba_tables and/or constrain the tablespace in the query):
create or replace procedure NG_DROP_TABLE(tableName varchar2)
is
c int;
begin
select count(*) into c from user_tables where table_name = upper(tableName);
if c = 1 then
execute immediate 'drop table '||tableName;
end if;
end;
If you are doing in code then first check for table in database
by using query
SELECT table_name
FROM user_tables
WHERE table_name = 'XYZ'
if record found then truncate table otherwise create Table
Work like Create or Replace.
You can use CORT (www.softcraftltd.co.uk/cort). This tool allows to CREATE OR REPLACE table in Oracle.
It looks like:
create /*# or replace */ table MyTable(
... -- standard table definition
);
It preserves data.
So I've been using this and it has worked very well: - it works more like a DROP IF EXISTS but gets the job done
DECLARE
VE_TABLENOTEXISTS EXCEPTION;
PRAGMA EXCEPTION_INIT(VE_TABLENOTEXISTS, -942);
PROCEDURE DROPTABLE(PIS_TABLENAME IN VARCHAR2) IS
VS_DYNAMICDROPTABLESQL VARCHAR2(1024);
BEGIN
VS_DYNAMICDROPTABLESQL := 'DROP TABLE ' || PIS_TABLENAME;
EXECUTE IMMEDIATE VS_DYNAMICDROPTABLESQL;
EXCEPTION
WHEN VE_TABLENOTEXISTS THEN
DBMS_OUTPUT.PUT_LINE(PIS_TABLENAME || ' NOT EXIST, SKIPPING....');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
RAISE;
END DROPTABLE;
BEGIN
DROPTABLE('YOUR_TABLE_HERE');
END DROPTABLE;
/
Hope this helps
Also reference:
PLS-00103 Error in PL/SQL Developer
'Create or replace table' is not possible. As others stated, you can write a procedure and/or use begin execute immediately (...). Because I don't see an answer with how to (re)create the table, I putted a script as an answer.
PS: in line of what jeffrey-kemp mentioned: this beneath script will NOT save data that is already present in the table you are going to drop. Because of the risk of loosing data, at our company it is only allowed to alter existing tables on the production environment, and it is not allowed to drop tables. By using the drop table statement, sooner or later you will get the company police standing at your desk.
--Create the table 'A_TABLE_X', and drop the table in case it already is present
BEGIN
EXECUTE IMMEDIATE
'
CREATE TABLE A_TABLE_X
(
COLUMN1 NUMBER(15,0),
COLUMN2 VARCHAR2(255 CHAR),
COLUMN3 VARCHAR2(255 CHAR)
)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN -- ORA-00955: object name already used
EXECUTE IMMEDIATE 'DROP TABLE A_TABLE_X';
END IF;
END;
I would do something like this
begin
for i in (select table_name from user_tables where table_name = 'FOO') loop
execute immediate 'drop table '||i.table_name;
end loop;
end;
execute immediate 'CREATE TABLE FOO (id NUMBER,
title VARCHAR2(4000)) ';
If this is for MS SQL.. The following code will always run no matter what if the table exist already or not.
if object_id('mytablename') is not null //has the table been created already in the db
Begin
drop table mytablename
End
Create table mytablename (...

Resources