Migrate Mutliple Columns to Single Column Object Oracle SQL - sql

I have the following columns: user_address, user_city, user_state, and user_zip.
I have created a new column that has a custom object datatype:
CREATE TYPE ADDRESS_ADT AS OBJECT (
address VARCHAR2(255),
city VARCHAR2(20),
state CHAR(2),
zip VARCHAR2(20)
);
ALTER TABLE
USERS
ADD (
ADDRESS ADDRESS_ADT
);
I want to write a PL/SQL Unnamed procedure to migrate addresses to this new single column but am not sure how to approach it. Any suggestions?

Create a cursor, loop through the table
Read into the cursor
Build your ADDRESS_ADT object
Update the table
Something like this:
declare
cursor users_cur is
select rowid, street_address as address, city, state, zip
from users;
type users_aat is table of users_cur%ROWTYPE index by pls_integer;
l_users users_aat;
l_address_adt address_adt;
begin
open users_cur;
loop
fetch users_cur bulk collect into l_users limit 100;
for i in 1..l_users.count
loop
l_address_adt := address_adt(
l_users(i).address,
l_users(i).city,
l_users(i).state,
l_users(i).zip
);
update users set address = l_address_adt
where rowid = l_users(i).rowid;
end loop;
exit when l_users.count < 100;
end loop;
commit;
close users_cur;
end;
/

Related

PL/SQL Bulk collect not enough values error

I have a users-table with values id, uname, pwd, email and more. Now i wanna create a nested table with only some of these values(id, uname, pwd - but no email):
create or replace TYPE type_u as object(type_id number(4,0), type_uname varchar(32), type_pwd varchar(16));
create or replace TYPE u_tbl as table of type_u;
now i want to fill this nested table with the data (id,uname,pwd) of the users table. I tried to use bulk collect for that:
SELECT u.id,u.uname,u.pwd BULK COLLECT INTO u_tbl FROM user_table u;
But i keep getting the error "not enough values". What is the error in that select statement? Thanks in advance!
Full Code:
create or replace TYPE type_u as object(type_id number(4,0), type_uname varchar(32), type_pwd varchar(16));
create or replace TYPE u_tbl as table of type_u;
create or replace PROCEDURE RET_STRING_TAB(o_cursor OUT SYS_REFCURSOR) IS
v_u_tbl u_tbl;
BEGIN
SELECT u.id, u.name, u.pwd BULK COLLECT INTO v_u_tbl FROM user_table u;
-- For each found User do something
FOR i IN 1 .. v_u_tbl.count
LOOP
-- do something
END LOOP;
OPEN o_cursor FOR SELECT * FROM TABLE(v_u_tbl);
END RET_STRING_TAB;
Please try this one
SELECT type_u(u.id,u.uname,u.pwd) BULK COLLECT INTO u_tbl FROM user_table u;

Oracle Procedure- Select all tables and then loop though the records of those tables

I got all the tables from my database that starts with "FULLTEXTLOOKTABLE_%"
Now i want to loop though all those tables.
Select each record from those tables and store the data in 4 variables.
Then delete that particular row in table
Insert into table using the data in those 4 variable.
I want to do this because there was a mismatch in the insertion of data in columns, i cannot alter the column name so i have to truncate the table re-insert correctly.
The code i wrote till now is:
create or replace PROCEDURE "Update"(name_in IN varchar2 )
AS
iID NUMBER(10,0);
FullTextDetails VARCHAR2(4000 BYTE);
Regex VARCHAR2(4000 BYTE);
MinMatch NUMBER(10,0);
BEGIN
FOR c IN ( SELECT table_name FROM user_tables WHERE table_name LIKE 'FULLTEXTLOOKTABLE_%' )
LOOP
// Loop Throgh all records of c.table_name
// insert the data into iID, FullTextDetails,Regex,MinMatch
//Delete that row
//Insert it again using the data in variables
END LOOP;
END;
Instead of processing record by record, you can do it at a table level.
Create a new table tab_dest having the same structure as the source table tab_src including data as follows -
CREATE TABLE TAB_DEST AS
SELECT * FROM TAB_SRC;
Truncate your source table as -
TRUNCATE TABLE TAB_SRC;
Insert the records from TAB_DEST back to TAB_SRC specifying the column order -
INSERT INTO TAB_SRC(COL1, COL2, COL3)
SELECT COL1, COL3 AS COL2, COL2 AS COL3
FROM TAB_DEST;
Hope this works for you.
you can try something like this but for this way, you need to create a procedure that returns reference cursor
create or replace PROCEDURE "Update"(name_in IN varchar2 )
AS
iID NUMBER(10,0);
FullTextDetails VARCHAR2(4000 BYTE);
Regex VARCHAR2(4000 BYTE);
MinMatch NUMBER(10,0);
TYPE cur_type IS REF CURSOR;
tmp_cursor cur_type;
BEGIN
FOR c IN ( SELECT table_name FROM user_tables WHERE table_name LIKE 'FULLTEXTLOOKTABLE_%' )
LOOP
tmp_cursor := prc_that_returns_ref_cursor(table_name);
//Delete the data from table
FOR tmp_cursor_rec IN tmp_cursor LOOP
//Insert it again using the data in tmp_cursor_rec.column name
END LOOP;
END;

value limitation in an IN clause Oracle

I work for a company that has a DW - ETL setup. I need to write a query that looks for over 2500+ values in an WHEN - IN clause and also over 1000+ values in a WHERE - IN clause. Basically it would look like the following:
SELECT
,user_id
,CASE WHEN user_id IN ('user_n', +2500 user_[n+1] ) THEN 1
ELSE 0
,item_id
FROM user_table
WHERE item_id IN ('item_n', +1000 item_[n+1] );
As you probably already know PL/SQL allows a maximum of 1000 values in an IN clause, so I tried adding OR - IN clauses (as suggested in other stackoverflow threads):
SELECT
,user_id
,CASE WHEN user_id IN ('user_n', +999 user_[n+1] )
OR user_id IN ('user_n', +999 user_[n+1] )
OR user_id IN ('user_n', +999 user_[n+1] ) THEN 1
ELSE 0 END AS user_group
,item_id
FROM user_table
WHERE item_id IN ('item_n', +999 item_[n+1] )
OR item_id IN ('item_n', +999 item_[n+1] );
NOTE: i know the math is erroneous in the examples above, but you get the point
The problem is that queries have a maximum executing time of 120 minutes and the job is being automatically killed. So I googled what solutions I could find and it seems Temporary Tables could be the solution I'm looking for, but with all honesty none of the examples I found is clear enough on how to include the values I want in the table and also how to use this table in my original query. Not even the ORACLE documentation was of much help.
Another potential problem is that I have limited rights and I've seen other people mention that in their companies they don't have the rights to create temporary tables.
Some of the info I found in my research:
ORACLE documentation
StackOverflow thread
[StackOverflow thread 2]
Another solution I found was using tuples instead, as mentioned in THIS thread (which I haven't tried) because as another user mentions performance seems greatly affected.
Any guidance on how to use a Temporary Table or if anyone has another way of dealing with this limitation would be greatly appreciated.
Create a global temporary table so no undo logs are created
CREATE GLOBAL TEMPORARY TABLE <table_name> (
<column_name> <column_data_type>,
<column_name> <column_data_type>,
<column_name> <column_data_type>)
ON COMMIT DELETE ROWS;
then depending on how the user list arrives import the data into a holding table and then run
select 'INSERT INTO global_temporary_table <column> values '
|| holding_table.column
||';'
FROM holding_table.column;
This gives you insert statements as output which you run to insert the data.
then
SELECT <some_column>
FROM <some_table>
WHERE <some_value> IN
(SELECT <some_column> from <global_temporary_table>
Use a collection:
CREATE TYPE Ints_Table AS TABLE OF INT;
CREATE TYPE IDs_Table AS TABLE OF CHAR(5);
Something like this:
SELECT user_id,
CASE WHEN user_id MEMBER OF Ints_Table( 1, 2, 3, /* ... */ 2500 )
THEN 1
ELSE 0
END
,item_id
FROM user_table
WHERE item_id MEMBER OF IDs_table( 'ABSC2', 'DITO9', 'KMKM9', /* ... */ 'QD3R5' );
Or you can use PL/SQL to populate a collection:
VARIABLE cur REFCURSOR;
DECLARE
t_users Ints_Table;
t_items IDs_Table;
f UTL_FILE.FILE_TYPE;
line VARCHAR2(4000);
BEGIN
t_users.EXTEND( 2500 );
FOR i = 1 .. 2500 LOOP
t_users( t_users.COUNT ) := i;
END LOOP;
// load data from a file
f := UTL_FILE.FOPEN('DIRECTORY_HANDLE','datafile.txt','R');
IF UTL_FILE.IS_OPEN(f) THEN
LOOP
UTL_FILE.GET_LINE(f,line);
IF line IS NULL THEN EXIT; END IF;
t_items.EXTEND;
t_items( t_items.COUNT ) := line;
END LOOP;
OPEN :cur FOR
SELECT user_id,
CASE WHEN user_id MEMBER OF t_users
THEN 1
ELSE 0
END
,item_id
FROM user_table
WHERE item_id MEMBER OF t_items;
END;
/
PRINT cur;
Or if you are using another language to call the query then you could pass the collections as a bind value (as shown here).
In PL/SQL you could use a collection type. You could create your own like this:
create type string_table is table of varchar2(100);
Or use an existing type such as SYS.DBMS_DEBUG_VC2COLL which is a table of VARCHAR2(1000).
Now you can declare a collection of this type for each of your lists, populate it, and use it in the query - something like this:
declare
strings1 SYS.DBMS_DEBUG_VC2COLL := SYS.DBMS_DEBUG_VC2COLL();
strings2 SYS.DBMS_DEBUG_VC2COLL := SYS.DBMS_DEBUG_VC2COLL();
procedure add_string1 (p_string varchar2) is
begin
strings1.extend();
strings1(strings.count) := p_string;
end;
procedure add_string2 (p_string varchar2) is
begin
strings2.extend();
strings2(strings2.count) := p_string;
end;
begin
add_string1('1');
add_string1('2');
add_string1('3');
-- and so on...
add_string1('2500');
add_string2('1');
add_string2('2');
add_string2('3');
-- and so on...
add_string2('1400');
for r in (
select user_id
, case when user_id in table(strings2) then 1 else 0 end as indicator
, item_id
from user_table
where item_id in table(strings1)
)
loop
dbms_output.put_Line(r.user_id||' '||r.indicator);
end loop;
end;
/
You can use below example to understand Global temporary tables and the type of GTT.
CREATE GLOBAL TEMPORARY TABLE GTT_PRESERVE_ROWS (ID NUMBER) ON COMMIT PRESERVE ROWS;
INSERT INTO GTT_PRESERVE_ROWS VALUES (1);
COMMIT;
SELECT * FROM GTT_PRESERVE_ROWS;
DELETE FROM GTT_PRESERVE_ROWS;
COMMIT;
TRUNCATE TABLE GTT_PRESERVE_ROWS;
DROP TABLE GTT_PRESERVE_ROWS;--WONT WORK IF YOU DIDNOT TRUNCATE THE TABLE OR THE TABLE IS BEING USED IN SOME OTHER SESSION
CREATE GLOBAL TEMPORARY TABLE GTT_DELETE_ROWS (ID NUMBER) ON COMMIT DELETE ROWS;
INSERT INTO GTT_DELETE_ROWS VALUES (1);
SELECT * FROM GTT_DELETE_ROWS;
COMMIT;
SELECT * FROM GTT_DELETE_ROWS;
DROP TABLE GTT_DELETE_ROWS;
However as you mentioned you receive the input in an excel file so you can simply create a table and load data in that table. Once the data is loaded you can use the data in IN clause of your query.
select * from employee where empid in (select empid from temptable);
create temporary table userids (userid int);
insert into userids(...)
then a join or in subquery
select ...
where user_id in (select userid from userids);
drop temporary table userids;

PL/SQL returning contents of a table

Currently working on a .Net application for viewing the contents of a table, I faced difficulties with the function that returns the contents of the table.
I use the Oracle WebLogic Server to interface comics and application, I have a function that returns a desired record of the table but I unable to adapt to it returns all records of table.
I used tables, objects I have not been able to operate the pipeline.
Here is the function that returns a record.
CREATE OR REPLACE TYPE DMD_REC AS OBJECT
(
matricule VARCHAR2(10),
nom VARCHAR2(15),
prenom VARCHAR2(15),
adresse VARCHAR2(10),
profile VARCHAR2(15),
service VARCHAR2(15),
date_dmd DATE
);
CREATE OR REPLACE FUNCTION aff_dmd
(dmd_mat IN VARCHAR2)
RETURN DMD_REC IS
dmd_found demande%rowtype;
dmd_rtn DMD_REC;
BEGIN
SELECT *
INTO dmd_found
FROM demande
WHERE demande.matricule=dmd_mat;
dmd_rtn := DMD_REC
(
dmd_found.matricule,
dmd_found.nom,
dmd_found.prenom,
dmd_found.adresse,
dmd_found.profile,
dmd_found.service,
dmd_found.date_dmd
);
RETURN dmd_rtn;
END aff_dmd;
Sorry for my english
Your function returns an object which fits one row. You need it to return a table of such objects.
So first you need a table type:
CREATE OR REPLACE TYPE DMD_NT AS TABLE OF DMD_REC;
Then you have to convert your function to use this new type. In the absence of any other requirements this revision will select all records in the demande table when a NULL is passed:
CREATE OR REPLACE FUNCTION aff_dmd
(dmd_mat IN VARCHAR2 := null)
RETURN DMD_NT
IS
dmd_rtn DMD_NT;
BEGIN
SELECT DMD_REC
(
d.matricule,
d.nom,
d.prenom,
d.adresse,
d.profile,
d.service,
d.date_dmd
)
BULK COLLECT INTO dmd_rtn
FROM demande
WHERE
( dmd_mat is null
or d.matricule = dmd_mat
);
RETURN dmd_rtn;
END aff_dmd;
/
The BULK COLLECT populates a collection which is stored in session memory. If your table is large (say more than 5000) this may create problems with memory resources. In which case you should consider creating a pipelined function:
CREATE OR REPLACE FUNCTION aff_dmd
(dmd_mat IN VARCHAR2 := null)
RETURN DMD_NT PIPELINED
IS
dmd_rtn DMD_NT;
BEGIN
loop
SELECT DMD_REC
(
d.matricule,
d.nom,
d.prenom,
d.adresse,
d.profile,
d.service,
d.date_dmd
)
BULK COLLECT INTO dmd_rtn limit 1000
FROM demande
WHERE
( dmd_mat is null
or d.matricule = dmd_mat
);
exit when dmd_rtn.count() = 0;
for idx in 1..dmd_rtn.count() loop
pipe row (dmd_rtn(idx));
end loop;
end loop
RETURN;
END aff_dmd;
Pipelined functions have an overhead. You should consider whether there is a way you can make it work with SYS_REFCURSOR as #BobJarvis suggested.

How to resolve the component must be declared using oracle Types

I have two oracle types like
create or replace
TYPE T_EMPLOYEE
AS TABLE OF O_EMPLOYEE;
And my O_EMPLOYEE TYPE is
create or replace
TYPE O_EMPLOYEE
AS OBJECT
(
EMP_NAME VARCHAR2(50),
EMP_ID VARCHAR2(50),
EMP_DES VARCHAR2(50)
);
I am using this as an input in a store procedure where i need to check for the validation of name,id and designation. Using following i can convert the table in a select statement.
TABLE(CAST( I_T_EMPLOYEE AS T_EMPLOYEE)) emp,
but I tried to read the value like like T_EMPLOYEE.EMP_NAME, it is saying componenet EMP_NAME must be decalred.
can any one help?
thank you shabilan. There are two ways we can retrieve the value. first one
By writing following in store procedure. you need to declare the variables for
IS
V_EMP_NAME VARCHAR2(50),
V_EMP_ID VARCHAR2(50),
V_EMP_DES VARCHAR2(50),
BEGIN
SELECT PROCESS_TYPE,REQUEST_TYPE,STATUS,EFFECTIVE_DATE_RANGE
INTO V_EMP_NAME, V_EMP_ID, V_EMP_DES
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
--validation part
if(V_EMP_NAME is null or V_EMP_ID or V_EMP_DES ) then RAISE NULL_DATA;
END IF;
This one will fail if you pass multiple Object Types in store procedure like
T_E_I :=T_EMPLOYEE(O_EMPLOYEE('Test','1234','manager'),
O_EMPLOYEE('Test','1234','manager'));
GET_DATA_PKG.EMPLOYEE_ORDER_BY_ROLE(T_E_I, :o_error_code,
:o_error_message);
2nd approach handles the multiple inputs. this is done using cursor.you need to declare the variables for
IS
OEM O_EMPLOYEE := O_EMPLOYEE(null,null,null);
c_EMP_NAME OEM.EMP_NAME%type,
c_EMP_ID OEM.EMP_ID%type,
c_EMP_DES OEM.EMP_DES%type,
Cursor emp_role_curs is
SELECT PROCESS_TYPE,REQUEST_TYPE,STATUS,EFFECTIVE_DATE_RANGE
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
BEGIN
OPEN emp_role_curs;
loop
fetch emp_role_curs into C_EMP_NAME, C_EMP_ID, C_EMP_DES
FROM TABLE(CAST(I_T_EMPLOYEE AS T_EMPLOYEE)) ITE;
--validation part
if(V_EMP_NAME is null or V_EMP_ID or V_EMP_DES ) then RAISE NULL_DATA;
END IF;
EXIT WHEN emp_role_curs%notfound;
END LOOP;
CLOSE emp_role_curs;
I hope this is useful if some one want to read the object type as a table.