I am using variable to store multiple rows and I want to insert it into temporary table.
This query returns multiple rows
BEGIN
SELECT id INTO
temp_var
FROM TABLE_1 a,
TABLE_2 b
where a.id =b.id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
temp_var := NULL;
END;
I want to insert variable values into temporary table?
Assumption: when you say "temporary table" you mean the SQL Server usage, which is a PL/SQL collection in Oracle.
Here a variable temp_var is a nested table with a composite structure which matches the projection of table_1.
declare
type table1_nt is table of table_1%rowtype;
temp_var table1_nt;
begin
select *
bulk collection into temp_var
from table_1;
....
end;
This gives you the contents of table_1 in an array which you can work with in PL/SQL. Find out more.
Given your revised requirement, it's easy enough to work with a single attribute instead:
declare
type id_nt is table of varchar2(20); -- table_1.id%type
temp_var id_nt;
begin
select a.id
bulk collection into temp_var
from table_1 a,
join table_2 b
on a.id =b.id; ;
....
end;
Related
I have a trigger whose purpose is to fire whenever there is a DELETE on a particular table and insert the deleted data into another table in json format.
The trigger works fine if I am specifying each column explicitly. Is there any way to access the entire table row?
This is my code.
TRIGGER1
AFTER DELETE
ON QUESTION
FOR EACH ROW
DECLARE
json_doc CLOB;
BEGIN
select json_arrayagg (
json_object ('code' VALUE :old.id,
'name' VALUE :old.text,
'description' VALUE :old.text) returning clob
) into json_doc
from dual;
PROCEDURE1(json_doc);
END;
This works fine. However, what I want is something like this. Instead of explicity specifying each column, I want to convert the entire :OLD data
TRIGGER1
AFTER DELETE
ON QUESTION
FOR EACH ROW
DECLARE
json_doc CLOB;
BEGIN
select json_arrayagg (
json_object (:old) returning clob
) into json_doc
from dual;
PROCEDURE1(json_doc);
END;
Any suggestion please.
The short and correct answer is you can't. We have a few tables in our application where we do this and the developer is responsible for updating the trigger when they add a column: this is enforced with code reviews and is probably the cleanest solution for this scenario.
The long answer is you can get close, but I wouldn't do this in production for several reasons:
Triggers are terrible for performance
Triggers are terrible for code clarity
This requires reading the row again using flashback query so
You aren't getting the values of this row from inside your current transaction: if you update the row in your transaction and then delete it the JSON will show what the values were BEFORE your update
There is a performance penalty for reading from UNDO
There is potential that UNDO won't be available and your trigger will fail
Your user needs permission to execute flashback queries
Your database needs to meet all the perquisites to support flashback queries
Deleting a lot of rows will cause the ROWID collection to get large and consume PGA
There are probably more reasons, but in the interest of "can it be done" here you go...
DROP TABLE t1;
DROP TABLE t2;
DROP TRIGGER t1_ad;
CREATE TABLE t1 (
id NUMBER,
name VARCHAR2(100),
description VARCHAR2(100)
);
CREATE TABLE t2 (
dt TIMESTAMP(9),
json_data CLOB
);
INSERT INTO t1 VALUES (1, 'A','aaaa');
INSERT INTO t1 VALUES (2, 'B','bbbb');
INSERT INTO t1 VALUES (3, 'C','cccc');
INSERT INTO t1 VALUES (4, 'D','dddd');
CREATE OR REPLACE TRIGGER t1_ad
FOR DELETE ON t1
COMPOUND TRIGGER
TYPE t_rowid_tab IS TABLE OF ROWID;
v_rowid_tab t_rowid_tab := t_rowid_tab();
AFTER EACH ROW IS
BEGIN
v_rowid_tab.extend;
v_rowid_tab(v_rowid_tab.last) := :old.rowid;
END AFTER EACH ROW;
AFTER STATEMENT IS
v_scn v$database.current_scn := dbms_flashback.get_system_change_number;
v_json_data CLOB;
v_sql CLOB;
BEGIN
FOR i IN 1 .. v_rowid_tab.count
LOOP
SELECT 'SELECT json_arrayagg(json_object(' ||
listagg('''' || lower(t.column_name) || ''' VALUE ' ||
lower(t.column_name),
', ') within GROUP(ORDER BY t.column_id) || ') RETURNING CLOB) FROM t1 AS OF SCN :scn WHERE rowid = :r'
INTO v_sql
FROM user_tab_columns t
WHERE t.table_name = 'T1';
EXECUTE IMMEDIATE v_sql
INTO v_json_data
USING v_scn, v_rowid_tab(i);
INSERT INTO t2
VALUES
(current_timestamp,
v_json_data);
END LOOP;
END AFTER STATEMENT;
END t1_ad;
/
UPDATE t1
SET NAME = 'zzzz' -- not captured
WHERE id = 2;
DELETE FROM t1 WHERE id < 3;
SELECT *
FROM t2;
-- 13-NOV-20 01.08.15.955426000 PM [{"id":1,"name":"A","description":"aaaa"}]
-- 13-NOV-20 01.08.15.969755000 PM [{"id":2,"name":"B","description":"bbbb"}]
I want to create a procedure in which I use a cursor to select certain lines and then insert them in another table. I wonder if there is a notation to write it faster.
For instance here is the complete procedure
create or replace procedure myProc as
Cursor lines is
select * from table1 where c = '2';
begin
for line in lines loop
insert into table2 values(line.a, line.b, line.c, line.d ....);
end loop;
end;
/
I want to know if I can replace the 'insert into' line by something like
insert into table2 values(line.something);
or
insert into tables2 values(something(line));
(I think a view could be more effective but it's not the question here.)
Absolutely:
create or replace procedure myProc as
begin
insert into table2( . . .)
select a, b, c, d, . .
from table1
where c = '2';
end;
/
You should list the columns in table2 as well. That is what the table2( . . . ) means.
Despite the following code should work in case structure of both tables were identical...
INSERT INTO target_table
SELECT * FROM source_table;
... you should avoid this way of programming because any column addition to source or target table will end in SQL became invalid.
Looks like you're looking for a short-cut in order to avoid qualifying column names in an insert statement.
Well, although I do not recommend this, it can be done bug it's a little tedious. Here is an example:
if you have a table employee (employee_id, employee_name, designation). You'll need to create two types in the database-
a. An object type which is similar to employee-
create type employee_obj as object (employee_id number(28,0),
employee_name varchar2(100),
designation varchar2(30));
b. Create the set type of this record-
create type employee_obj_set is table of employee_obj;
c. In your PL/SQL procedure you can use something like:
DECLARE
empset employee_obj_set;
-- More variables here
BEGIN
-- other operations
-- populate your empset
-- other operations
INSERT INTO employee
SELECT * FROM table(empset);
-- other operations
END;
/
For faster inserts use bulk collect/FORALL insert instead of singular inserts.Find below sample...
DECLARE
CURSOR lines is select * from table1 where c = '2';
type lines_ty is table of table2%rowtype;
l_lines_t2 lines_ty;
BEGIN
OPEN lines;
LOOP
FETCH lines BULK COLLECT INTO l_lines_t2 LIMIT 500;
FORALL i IN 1..l_lines_t2.COUNT
INSERT INTO table2 VALUES l_lines_t2(i);
EXIT WHEN lines%NOTFOUND;
END LOOP;
CLOSE lines;
END;
colleagues
I have a problem with table function in Oracle.
More specifically, I have a function that converts BLOB into table of varchar2.
create type string_array is table of varchar2(255);
create or replace function blob_to_strings(p_blb in BLOB) return string_array as
begin
-- some processing here
end;
Also I have table containing BLOBS I need to work with.
create table my_blobs (id number, data blob)
Now, having id in my_blobs table, I want to query result of convert function. Something like
select t.* from table(blob_to_strings(b.data)) t, my_blobs b where b.id = 123;
(I know this is incorrect, just showing what I need)
This query expectedly returns b.data: invalid identifier as you can't access other table columns inside from section.
I understand how to do it in PL/SQL running 2 queries, but really need to do it in SQL.
Can anybody help me?
Thank you in advance.
UPD: I tried following:
select * from table(select blob_to_strings(b.data) from my_blobs b where b.id = 123);
Result: ORA-00902: invalid datatype
Any other ideas?
Possibly the issue with your original query was that you had the table name coming after the attempt to select from the array (from table(blob_to_strings(b.data)) t, my_blobs b). In other words, you were trying to select from something that hadn't yet been declared. Switch the order of the items in the from clause, and it should work.
Here's a test case that I knocked up to demonstrate (I used CLOBs since we're apparently dealing with text; I'm not sure why you're using BLOBs?):
create table t1 (id number,
clob_col clob);
insert into t1 values (1, 'abcd');
insert into t1 values (2, 'efg');
commit;
create type string_array is table of varchar2(255);
create or replace function clob_to_str_tab (p_clob in clob)
return string_array
is
v_str_arr string_array := string_array();
begin
for i in 1.. length(p_clob)
loop
v_str_arr.extend;
v_str_arr(i) := substr(p_clob, i, 1);
end loop;
return v_str_arr;
end;
/
select t1.id,
t2.column_value res
from table(clob_to_str_tab(t1.clob_col)) t2,
t1;
ORA-00904: "T1"."CLOB_COL": invalid identifier
select t1.id,
t2.column_value res
from t1,
table(clob_to_str_tab(t1.clob_col)) t2;
ID RES
---------- ---
1 a
1 b
1 c
1 d
2 e
2 f
2 g
You can achieve this with Oracle's PIPE ROW statement
See: Pipelined Table Functions
I make to try a search engine.Scenario like that; I have a table that it contains text context and I'm fetching some records what suitable according to my query then I want to transfer this founded text of id on a table that created dynamicly on runtime. My sql code as follow but this error
"expression is of wrong type"
SQL
declare
v_table dbms_sql.number_table;
begin
select a_id bulk collect into v_table from (select a_id from b where length(b_data) > 4);
select * from a where a_id in v_table;
end;
DBMS_SQL.NUMBER_TABLE is an associative array:
Unlike a database table, an associative array:
Does not need disk space or network operations
Cannot be manipulated with DML statements
You can select into an associative array, but you cannot use it as a table in a select.
You could to the select into with a nested table but you still couldn't use that in a select if the type is declared within your block because it would be a PL/SQL type that isn't valid within an SQL statement.
You would need a nested table type defined in SQL (i.e. not in PL/SQL) to achieve this. There is a built-in type to make it easier, but you can define your own:
declare
v_table sys.odcinumberlist;
v_table2 sys.odcinumberlist;
begin
select a_id bulk collect into v_table
from (select a_id from b where length(b_data) > 4);
select a.a_id bulk collect into v_table2
from table(v_table) vt
join a on a.a_id = vt.column_value;
end;
/
anonymous block completed
The second select you showed is incomplete so I've made one up. Your first doesn't need the nested select, that can be simplified to:
select a_id bulk collect into v_table
from b
where length(b_data) > 4;
Hopefully you're dong something with the collection before your second select, otherwise it's a bit pointless; you could just join a to b in the second select to get the same result:
select a.<columns> ...
from a
join b on b.a_id = a.a_id
where length(b.b_date) > 4;
As we can select multiple rows into single variable declared as some_table.rowtype in SQL. In the same way I want to fetch multiple row values of single column into a variable. So,
How can I declare it's variable type?
Can I use that in where clause?
Can I iterate through that variable(collection) values?
I want to do it in a stored procedure to delete one or more table records matching that variable values.
Does this work for you?
DELETE FROM myTable
WHERE myTable.ID IN
(SELECT ID
FROM myOtherTable
WHERE myOtherCondition)
CREATE TYPE number_table AS TABLE OF NUMBER;
/
DECLARE
my_number_table number_table;
BEGIN
SELECT some_column BULK COLLECT INTO my_number_table FROM some_table;
DELETE FROM some_other_table WHERE some_column IN (
SELECT column_value FROM TABLE(CAST(my_number_table AS number_table))
);
END;
/