How to duplicate rows with a column change for a list of tables? - oracle

Given the following example:
BEGIN
FOR r IN (
SELECT * FROM table_one WHERE change_id = 0
) LOOP
r.change_id := -1;
INSERT INTO table_one VALUES r;
END LOOP;
END;
This inserts new rows to table_one with the exact same content, except the intended change on column change_id to the value -1. I don't have to specify the columns inside of the script as I have to in an INSERT INTO table_one (change_id, ...) SELECT -1, ... FROM table_one WHERE change_id=0;
It works perfectly fine. But how to modify this script to work with a list of tables? The internal structure of those tables are different, but all of them have the necessary column change_id.
Of course the easiest solution would be to copy and paste this snippet x-times and replace the fix table name inside. But is there an option to work with a list of tables in an array?
My approach was like this:
DECLARE
TYPE tablenamearray IS VARRAY(30) OF VARCHAR2(30);
tablenames tablenamearray;
BEGIN
tablenames := tablenamearray('TABLE_ONE', 'TABLE_TWO', 'TABLE_THREE'); -- up to table 30...
FOR i IN tablenames.first..tablenames.last LOOP
/* Found no option to use tablenames(i) here with dynamic SQL */
END LOOP;
END;
Note: There is no technical primary key like an id with a sequence behind. The primary key is build by three columns incl. the change_id column.

You cannot create a SQL statement where the statement is not known at parse time. So, you cannot have a variable as a table name. What you're looking for is Dynamic SQL, which is a fairly complicated topic, but basically you're going to wind up building a SQL statement with DBMS_SQL or running a statement as a string with EXECUTE IMMEDIATE.

Related

Function results column names to be used in select statement

I have function which returns column names and i am trying to use the column name as part of my select statement, but my results are coming as column name instead of values
FUNCTION returning column name:
get_col_name(input1, input2)
Can И use this query to the results of the column from table -
SELECT GET_COL_NAME(input1,input2) FROM TABLE;
There are a few ways to run dynamic SQL directly inside a SQL statement. These techniques should be avoided since they are usually complicated, slow, and buggy. Before you do this try to find another way to solve the problem.
The below solution uses DBMS_XMLGEN.GETXML to produce XML from a dynamically created SQL statement, and then uses XML table processing to extract the value.
This is the simplest way to run dynamic SQL in SQL, and it only requires built-in packages. The main limitation is that the number and type of columns is still fixed. If you need a function that returns an unknown number of columns you'll need something more powerful, like the open source program Method4. But that level of dynamic code gets even more difficult and should only be used after careful consideration.
Sample schema
--drop table table1;
create table table1(a number, b number);
insert into table1 values(1, 2);
commit;
Function that returns column name
create or replace function get_col_name(input1 number, input2 number) return varchar2 is
begin
if input1 = 0 then
return 'a';
else
return 'b';
end if;
end;
/
Sample query and result
select dynamic_column
from
(
select xmltype(dbms_xmlgen.getxml('
select '||get_col_name(0,0)||' dynamic_column from table1'
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns dynamic_column varchar2(4000) path 'DYNAMIC_COLUMN'
);
DYNAMIC_COLUMN
--------------
1
If you change the inputs to the function the new value is 2 from column B. Use this SQL Fiddle to test the code.

Auditing a table with many columns without Fine Grained Auditing

I have to create a trigger for a table with many columns and I want to now if is any possibility to avoid using the name of the column after :new and :old. Instead of specifically use the column name I want to use the element from collection with column names of target table (the table on which the trigger is set).
The line 25 is that with the binding error:
DBMS_OUTPUT.PUT_LINE('Updating customer id'||col_name(i)||to_char(:new.col_name(i)));
Bellow you can see my trigger:
CREATE OR REPLACE TRIGGER TEST_TRG BEFORE
INSERT OR
UPDATE ON ITEMS REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW DECLARE TYPE col_list IS TABLE OF VARCHAR2(60);
col_name col_list := col_list();
total INTEGER;
counter INTEGER :=0;
BEGIN
SELECT COUNT(*)
INTO total
FROM user_tab_columns
WHERE table_name = 'ITEMS';
FOR rec IN
(SELECT column_name FROM user_tab_columns WHERE table_name = 'ITEMS'
)
LOOP
col_name.extend;
counter :=counter+1;
col_name(counter) := rec.column_name;
dbms_output.put_line(col_name(counter));
END LOOP;
dbms_output.put_line(TO_CHAR(total));
FOR i IN 1 .. col_name.count
LOOP
IF UPDATING(col_name(i)) THEN
DBMS_OUTPUT.PUT_LINE('Updating customer id'||col_name(i)||to_char(:new.col_name(i)));
END IF;
END LOOP;
END;
Sincerely,
After digging more I have found that is not possible to dynamically reference the :new.column_name or :old.column_name values in a trigger. Due to this I will use my code only to INSERT (it does not have an old value :-() and I will do some code in java to generate UPDATE statements.
I must refine my previous answer based on what has been said by Justin Cave and also my findings. We can create a dynamic list of values triggered by INSERTING and UPDATING, based on referencing clause (old and new). For example I have created 2 collections of type nested table with varchars. One collection will contain all column tabs, as strings, that I will use for auditing and another collection will contains values for that columns with binding reference (ex. :new.). After INSERTING predicate I have created a index by collection (an associative array) of strings with ID taken from list of strings with column tab name and the value taken from the list of values for that columns referenced by new. Due to the index by collection you have a full working dynamic list at your disposal. Good luck :-)

Inserting a record in PL/SQL block using for loop is yielding error

I am creating a table of inetegers called 'integer_properties' from 1 to 1000 the table columns are:
integer,isPrime,isOdd,isEven,digitCount;
I want to insert records using a for loop i tried following but the error says: 'missing SELECT keyword'
BEGIN
for k in 1..1000
loop
insert into integer_properties(integer,
isPrime,
isEven,
isOdd,
digitCount)
values(k,null,null,null,null);
end loop;
END;
It is tedious to enter 1000 numbers with DDL command without using PL/SQL block. I am trying to enter the loop variable values in the integer column. Is it possible to do that?
You can do it in a single query.
insert into integer_properties
select level, null, null, null, null
from dual
connect by level <= 1000;
commit;
LEVEL is a pseudocolumn used in hierarchical queries.
Your code seems fine, but using INTEGER as column name might be is causing problem. If you enclose it in double quotes, it will work fine. So, better to avoid using keywords while naming columns.

PL/SQL procedure - too many values

I'm sure this is something simple, but I'm really new to PL/SQL and this has me stuck.
I've written a simple stored procedure to return a few values about a customer. Right off the bat, the %rowtype's are not coming up as reserved keywords but the compiler isn't flagging those as errors.
It is, however, ignoring the entire SQL statement flagging the line FROM demo_customers as too many values. Even if I try reducing it to only select one column it still gives me the same error.
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT customer_id, cust_first_name, cust_last_name, cust_email
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you want to select into a %ROWTYPE record, you'll want to do a SELECT * rather than selecting individual columns
create or replace
PROCEDURE GETCUSTOMER
(
arg_customerID demo_customers.customer_id%type,
returnRec OUT demo_customers%rowtype
)
AS
BEGIN
SELECT *
INTO returnRec
FROM demo_customers
WHERE customer_id = arg_customerID ;
END GETCUSTOMER;
If you select 4 columns explicitly, Oracle expects you to have 4 variables to select those values into.

Can a table variable be used in a select statement where clause?

I have a stored procedure that is doing a two-step query. The first step is to gather a list of VARCHAR2 type characters from a table and collect them into a table variable, defined like this:
TYPE t_cids IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
v_cids t_cids;
So basically I have:
SELECT item BULK COLLECT INTO v_cids FROM table_one;
This works fine up until the next bit.
Now I want to use that collection in the where clause of another query within the same procedure, like so:
SELECT * FROM table_two WHERE cid IN v_cids;
Is there a way to do this? I am able to select an individual element, but I would like to use the table variable like a would use a regular table. I've tried variations using nested selects, but that doesn't seem to work either.
Thanks a lot,
Zach
You have several choices as to how you achieve this.
If you want to use a collection, then you can use the TABLE function to select from it but the type of collection you use becomes important.
for a brief example, this creates a database type that is a table of numbers:
CREATE TYPE number_tab AS TABLE OF NUMBER
/
Type created.
The next block then populates the collection and performs a rudimentary select from it using it as a table and joining it to the EMP table (with some output so you can see what's happening):
DECLARE
-- Create a variable and initialise it
v_num_tab number_tab := number_tab();
--
-- This is a collection for showing the output
TYPE v_emp_tabtype IS TABLE OF emp%ROWTYPE
INDEX BY PLS_INTEGER;
v_emp_tab v_emp_tabtype;
BEGIN
-- Populate the number_tab collection
v_num_tab.extend(2);
v_num_tab(1) := 7788;
v_num_tab(2) := 7902;
--
-- Show output to prove it is populated
FOR i IN 1 .. v_num_tab.COUNT
LOOP
dbms_output.put_line(v_num_tab(i));
END LOOP;
--
-- Perform a select using the collection as a table
SELECT e.*
BULK COLLECT INTO v_emp_tab
FROM emp e
INNER JOIN TABLE(v_num_tab) nt
ON (e.empno = nt.column_value);
--
-- Display the select output
FOR i IN 1 .. v_emp_tab.COUNT
LOOP
dbms_output.put_line(v_emp_tab(i).empno||' is a '||v_emp_tab(i).job);
END LOOP;
END;
You can see from this that the database TYPE collection (number_tab) was treated as a table and could be used as such.
Another option would be to simply join your two tables you are selecting from in your example:
SELECT tt.*
FROM table_two tt
INNER JOIN table_one to
ON (to.item = tt.cid);
There are other ways of doing this but the first might suit your needs best.
Hope this helps.
--Doesn't work.
--SELECT item BULK COLLECT AS 'mySelectedItems' INTO v_cids FROM table_one;
SELECT table_two.*
FROM table_two INNER JOIN v_cids
ON table_two.paramname = v_cids.mySelectedItems;
Unless I'm misunderstanding the question, this should only return results that are in the table variable.
Note: I've never used Oracle, but I imagine this case would be the same.

Resources