ORACLE's dbms_redefinition.copy_table_dependents copies indexes/triggers/constraints and renames them to
TMP$$_[original object name]
Is it possible to change the string "TMP$$_" , so that copy_table_dependents uses a different prefix?
I know I can rename the objects afterwards, but I wonder whether one can change the prefix.
No, you can't change the object_name.
I don't think this matters much, as the TMP$$ objects are removed once you run DBMS_REDEFINITION.FINISH_REDEF_TABLE.
If you assume the following environment
create table test_redef_source ( id number, tstamp date, primary key (id) );
create or replace trigger tr_test_redef_sources
before insert on test_redef_source
for each row
begin
:new.tstamp := sysdate;
end;
/
create table test_redef_int as
select * from test_redef_source;
declare
l_errors number;
begin
dbms_redefinition.start_redef_table (
uname => user
, orig_table => 'TEST_REDEF_SOURCE'
, int_table => 'TEST_REDEF_INT'
);
dbms_redefinition.copy_table_dependents (
uname => user
, orig_table => 'TEST_REDEF_SOURCE'
, int_table => 'TEST_REDEF_INT'
, copy_indexes => 1
, num_errors => l_errors
);
end;
/
You can see by querying USER_DEPENDENCIES; that the TMP$$ objects are dependent on the interim table:
select name, type, referenced_name
from user_dependencies
where referenced_name like '%TEST_REDEF%'
;
NAME TYPE REFERENCED_NAME
------------------------------ ------------------ --------------------
TMP$$_TR_TEST_REDEF_SOURCES0 TRIGGER TEST_REDEF_INT
TR_TEST_REDEF_SOURCES TRIGGER TEST_REDEF_SOURCE
When you've completed the redefinition; these objects are removed:
begin
dbms_redefinition.finish_redef_table (
uname => user
, orig_table => 'TEST_REDEF_SOURCE'
, int_table => 'TEST_REDEF_INT'
);
end;
/
PL/SQL procedure successfully completed.
select name, type, referenced_name
from user_dependencies
where referenced_name like '%TEST_REDEF%'
;
NAME TYPE REFERENCED_NAME
------------------------------ ------------------ --------------------
TR_TEST_REDEF_SOURCES TRIGGER TEST_REDEF_SOURCE
As they're only temporary objects, and the names are guaranteed to be unique; I don't see any particular problem with maintaining the standard name.
Related
Please anyone can assist me. I have a table type object.
CREATE OR REPLACE TYPE SI_PHONE_ACC_T AS TABLE OF SI_PHONE_ACC;
CREATE OR REPLACE TYPE SI_PHONE_ACC AS OBJECT
(
PHONE_ACC VARCHAR2(15);
);
Below is generally passed to the procedure as an input from UI end.
Example:
SI_PHONE_ACCT_T( SI_PHONE_ACC('123-345-6543'),SI_PHONE_ACC('999-999-9999'), SI_PHONE_ACC( 'ax878974545787wp')); -- first 2 are phone no., 3rd is account no.
Phonetable:
Emp_id--- phone_number
1 -- 123-345-6543
2 -- 999-999-9999
3--- 897-897-8781
Account table:
Emp_id--- account_number
10 -- A0000
20 -- B0000
30--- ax878974545787wp
CREATE OR REPLACE PACKAGE BODY order_mgr
IS
PROCEDURE ins_trees ( p_emp_details_in IN SI_PHONE_ACC_T )
BEGIN
-- Now, I need to retrieve emp_ids from Phone and Account tables based on the phone or account numbers passed in the input parameter. please let me know how to do this.
---once I get those emp_id's, i need to insert into employee table which contains only one column emp_id.
--Please let me know how to do this.
FOR i IN 1 .. p_emp_details_in.count
LOOP
INSERT into employee (emp_id)
values(??);
END IF;
END LOOP;
END;
PROCEDURE ins_trees ( p_emp_details_in IN SI_PHONE_ACC_T )
BEGIN
INSERT into employee (emp_id)
select Emp_id
from table(p_emp_details_in) t
, Phone_table t2
where t.PHONE_ACC = t2.phone_number
union
select Emp_id
from table(p_emp_details_in) t
, Account_table t2
where t.PHONE_ACC = t2.account_number
;
END;
/
How to define a record type in PL/SQL anonymous block that contains a property that is a collection of itself? Look at the following example:
DECLARE
type t_item is record (
name varchar2(64),
children t_items -- referencing t_items type
);
type t_items is table of t_item; -- referencing t_item type
BEGIN
-- script code
END
PL/SQL has no type hoisting so Oracle engine raises an exception:
PLS-00498: illegal use of a type before its declaration
How to define a record t_item that contains a table of t_item in its property children?
You can use objects defined in the SQL Scope using inheritance:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TYPE abstract_item IS OBJECT (
name VARCHAR2(64)
) NOT FINAL NOT INSTANTIABLE
/
CREATE TYPE t_items IS TABLE OF abstract_item
/
CREATE TYPE t_item UNDER abstract_item (
children t_items
) INSTANTIABLE
/
Query 1:
SELECT t_item(
'1',
t_items(
t_item( '1.1', t_items() ),
t_item(
'1.2',
t_items(
t_item( '1.2.1', null )
)
),
t_item( '1.3', null )
)
)
FROM DUAL
Results: (SQLFiddle doesn't display it nicely - but it runs without errors)
| T_ITEM('1',T_ITEMS(T_ITEM('1.1',T_ITEMS()),T_ITEM('1.2',T_ITEMS(T_ITEM('1.2.1',NULL))),T_ITEM('1.3',NULL))) |
|-------------------------------------------------------------------------------------------------------------|
| oracle.sql.STRUCT#2a094aab |
You could use a similar declaration in PL/SQL:
DECLARE
items t_item;
BEGIN
items = t_item( 'Item Name', t_items( /* ... */ ) );
END;
/
An example with reference to objects:
create or replace type item; -- forward declaration
/
create or replace type l_item_ref is table of ref item;
/
create or replace type item is object( a number, list l_item_ref)
/
CREATE TABLE t_item OF item nested table list store as ref_items
/
declare
v_list l_item_ref;
begin
insert into t_item values(1,null);
insert into t_item values(2,null);
insert into t_item values(3,null);
select ref(p) bulk collect into v_list from t_item p;
insert into t_item values(123,v_list);
commit;
end;
select p.a,p.list from t_item p;
Below is an example of create a custom record type and then create table type using the custom record type.
In your spec file you can define the custom type as below.
TYPE STUDENT IS RECORD
("ID" NUMBER,
"NAME" VARCHAR2(100)
);
TYPE STUDENT_TABLE IS TABLE OF STUDENT;
Now in your package body you can use the custom table which you have defined as below.
students STUDENT_TABLE; (Declaration of variable of type student_table)
As this does not work as a table you can not do direct insert on this. And one more thing to remember as in table if you insert record one after another it basically makes a new entry but in case of custom record type will replace the old one.
**SELECT
123 AS ID,
abc AS NAME
BULK COLLECT INTO STUDENT_TABLE FROM DUAL;** --(This is how we insert)
Consider if we execute same statement with different values again it will override the previous one as explained above.
So if you have a requirement to append records then you can follow the below approach.
students STUDENT_TABLE;
studentsTemp STUDENT_TABLE; (Declare a temp also to store the previous value)
**SELECT
123 AS ID,
abc AS NAME
BULK COLLECT INTO STUDENT_TABLE FROM DUAL;
studentsTemp := STUDENT_TABLE;
SELECT
789 AS ID,
xyz AS NAME
BULK COLLECT INTO STUDENT_TABLE FROM DUAL;
studentsTemp := students multiset union all studentsTemp;** --(This is how you can append)
Below is the way to return it through cursor.
OPEN CursorStudent FOR
SELECT ID,
NAME,
FROM TABLE(studentsTemp); (Will return all records.)
Advantage of using this is you can avoid temporary tables and less scripts to maintain when moving to higher environments.
Imagine this two tables
CREATE TABLE A
(
idA smallint primary key,
idP smallint ,
...
);
CREATE TABLE P
(
idP smallint primary key,
Type char , (ex:A, B, C)
...
);
I would like to behaviour like if in table A doesnt have the same type as in table P, is not for example a worker
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
IF((SELECT tp.Type
FROM P tp
WHERE tp.idP=:new.idP)!='W')
THEN RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
I believe somethin is wrong
IN PL/SQL you need to select into a variable. But gmiley is correct, a foreign key or virtual column with an index is a better solution.
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
v_type CHAR(3);
BEGIN
SELECT tp.Type
INTO v_type
FROM P tp
WHERE tp.idP=:new.idP;
IF v_type != 'W'
THEN
RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
You have to:
rename the column 'type' as this is a keyword and you'll get a lot of troubles when using it
add variable x to the declare section
use SELECT col INTO x construct
use x in the IF construct
I first create an address_type object
CREATE TYPE address_type AS OBJECT
( line1 VARCHAR2(100)
, line2 VARCHAR2(100)
, line3 VARCHAR2(100)
, city VARCHAR2(50)
, state VARCHAR2(50)
, country VARCHAR2(50)
, zip VARCHAR2(10)
);
/
I create a nested table type of the above object.
CREATE TYPE address_table AS TABLE OF ADDRESS_TYPE;
/
I then create another object as follows:
CREATE TYPE telephone_number_type AS OBJECT
( country_code VARCHAR2(4)
, area_code VARCHAR2(10)
, phone_number VARCHAR2(10)
, extension VARCHAR2(10)
, number_type VARCHAR2(10)
);
/
And then I create a nested table type as follows:
CREATE TYPE telephone_number_table AS TABLE OF TELEPHONE_NUMBER_TYPE;
/
Now I create a table named person. Many of whose columns are not much useful in this question, except for the telephone_numbers column which is of nested table telephone_number_table type.
CREATE TABLE person
( personid INTEGER PRIMARY KEY
, fname VARCHAR2(50) NOT NULL
, mname VARCHAR2(50)
, lname VARCHAR2(50) NOT NULL
, email VARCHAR2(255) UNIQUE
, password VARCHAR2(255) NOT NULL
, birthdate DATE
, billing_address ADDRESS_TABLE
, delivery_address ADDRESS_TABLE
, telephone_numbers TELEPHONE_NUMBER_TABLE
, display_pic BLOB
, ts_registration TIMESTAMP
, ts_verification TIMESTAMP
, ts_last_updated TIMESTAMP
) NESTED TABLE billing_address STORE AS nt_billing_address
, NESTED TABLE delivery_address STORE AS nt_delivery_address
, NESTED TABLE telephone_numbers STORE AS nt_telephone_numbers
, LOB(display_pic) STORE AS SECUREFILE (
TABLESPACE users
ENABLE STORAGE IN ROW
CHUNK 4096
PCTVERSION 20
NOCACHE
NOLOGGING
COMPRESS HIGH
)
;
I then create a sequence for this:
CREATE SEQUENCE sq_personid;
To insert values into the person table I use an anonymous block as follows:
DECLARE
v_fname person.fname%TYPE := 'Yogeshwar';
v_mname person.mname%TYPE := '';
v_lname person.lname%TYPE := 'Rachcha';
v_email person.email%TYPE := 'yogeshrachcha#gmail.com';
v_password person.password%TYPE := 'mail_123';
v_birthdate person.birthdate%TYPE := TO_DATE('28-03-1987', 'DD-MM-YYYY');
v_telephone_numbers TELEPHONE_NUMBER_TABLE;
v_billing_address ADDRESS_TABLE;
v_delivery_address ADDRESS_TABLE;
BEGIN
v_telephone_numbers := TELEPHONE_NUMBER_TABLE
( TELEPHONE_NUMBER_TYPE('+91','22','123456','','Residence')
, TELEPHONE_NUMBER_TYPE('+91','22','456798','123','Office')
, TELEPHONE_NUMBER_TYPE('+91','','1234567890','','Mobile'));
v_billing_address := ADDRESS_TABLE (ADDRESS_TYPE ( 'Line 1', 'Line 2', 'Line 3', 'Mumbai', 'Maharashtra', 'India', '123456'));
v_delivery_address := ADDRESS_TABLE (ADDRESS_TYPE ( 'Line 1', 'Line 2', 'Line 3', 'Mumbai', 'Maharashtra', 'India', '123456'));
-- billing and delivery addresses are the same. These are not much importance in this question.
INSERT INTO person VALUES
( sq_personid.nextval
, v_fname
, v_mname
, v_lname
, v_email
, v_password
, v_birthdate
, v_billing_address
, v_delivery_address
, v_telephone_numbers
, NULL
, sysdate
, sysdate
, sysdate);
END;
Everything runs absolutely perfect till this point. Then in an anonymous block like the following, I try to create a nested table variable and use it in an SQL query:
DECLARE
TYPE t_country_codes IS TABLE OF VARCHAR2(4);
country_codes T_COUNTRY_CODES := T_COUNTRY_CODES('+1', '+44', '+91');
cc VARCHAR2(4);
BEGIN
FOR i IN (SELECT t.country_code
FROM person p
CROSS JOIN TABLE(p.telephone_numbers) t
WHERE t.country_code IN (SELECT COLUMN_VALUE -- I doubt the problem is with this SELECT statement.
FROM TABLE(country_codes))) LOOP
dbms_output.put_line(i.country_code);
END LOOP;
END;
/
I get this error:
ORA-06550: line 8, column 70:
PLS-00642: local collection types not allowed in SQL statements
ORA-06550: line 8, column 64:
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
A nested table type can be declared either in SQL (via the CREATE TYPE statement like you did with the telephone_number_table type) or in PL/SQL (via the TYPE declaration on the DECLARE block). If you declare the type in PL/SQL, you cannot use an instance of the type in SQL. You would need to declare the type in SQL in order to use an instance of the type in SQL.
Please, try to move your "local" (from last block) collection definition to the schema level.
Also, this post can be helpful.
I created the following simple PL/SQL stored procedure example to ask a specific question. This procedure inserts an employee name and id number into a table called employees_???. The ??? is explained below.
PROCEDURE hire_employee (emp_id IN INTEGER, name IN VARCHAR2, country IN VARCHAR2)
AS
BEGIN
INSERT INTO employees_??? VALUES (emp_id, name, 1000);
END hire_employee;
What I need is to set the table name based on the IN variable country. For example,
If country = 'usa', I want the INSERT line to read:
INSERT INTO employees_usa VALUES (emp_id, name, 1000);
If country = 'germany', I want the INSERT line to read:
INSERT INTO employees_germany VALUES (emp_id, name, 1000);
If country = 'france', I want the INSERT line to read:
INSERT INTO employees_france VALUES (emp_id, name, 1000);
etc...
Is there a way to do this in PL/SQL by substituting something in place of employee_??? so only one line of code for INSERT is used? Or is using a case or if/then/else statement the best way?
To answer your question, you have to use execute immediate and create your statement dynamically.
create or replace procedure hire_employee (
emp_id IN INTEGER
, name IN VARCHAR2
, country IN VARCHAR2 ) is
-- maximum length of an object name in Oracle is 30
l_table_name varchar2(30) := 'employees_' || country;
begin
execute immediate 'insert into ' || l_table_name
|| ' values (:1, :2, 1000)'
using emp_id, name;
end hire_employee;
However, this is a massively over-complicated way of storing the data. If you want to select all data you have to union large numbers of tables.
It would be far better to normalise the database properly and add country to an employees table.
Something like the following:
create table employees (
emp_id number(16)
, country varchar2(3) -- ISO codes
, name varchar2(4000) -- maximum who knows what name people might have
, < other_columns >
, constraint pk_employees primary key ( emp_id )
);
Your procedure then becomes a very simple insert statement:
create or replace procedure hire_employee (
emp_id in integer
, name in varchar2
, country in varchar2 ) is
insert into employees
values ( emp_id, country, name, 1000 );
end hire_employee;
You can use dynamic SQL and the EXECUTE IMMEDIATE construct. In this, you construct the query as a string and then execute it. A good example is at http://docs.oracle.com/cd/B10500_01/appdev.920/a96590/adg09dyn.htm