Copy nested table's values in another tuple of same table - oracle11g

I have the following table in oracle 11g named parlamentari:
cf varchar(16)
nome varchar(20)
cognome varchar(20)
telefoni telefoni_NT
where telefoni_NT is a nested table of varchar2.
Now, I must to copyed the elements of the nested table of a tuple (sorgente) in another tuple (ricevente).
I try to write an example
Start situation
parlamentari
-------------------------------------
cf | nome | cognome | telefoni
-------------------------------------
1 | a | aa | VARCHAR(222,444)
2 | b | bb | VARCHAR(111)
Situation after procedure called
parlamentari
-------------------------------------
cf | nome | cognome | telefoni
-------------------------------------
1 | a | aa | VARCHAR(222,444)
2 | b | bb | VARCHAR(111, 222, 444)
I tried to written this procedure without success
create or replace procedure copia_telefoni2
(sorgente in parlamento2018.parlamentari.cf%type,
ricevente in parlamento2018.parlamentari.cf%type) as
cursor cur_out_tel is
select column_value as original_list
from parlamentari, table(telefoni)
where cf = sorgente
;
cursor in_parlamentare is
select column_value as copied_list
from parlamentari, table(telefoni)
where cf = destinazione;
begin
if (sorgente <> destinazione) then
for i in cur_out_tel loop
dbms_output.put_line(i.original_list);
insert into table(select telefoni from parlamentari where cf=destinazione) values (telefoni_nt(i.original_list));
end loop;
else
dbms_output.put_line('Errore! Sorgente e destinazione uguali');
end if;
end copia_telefoni2;

Copy nested table's values in another tuple of same table
As far i could understand your requirement, you don't need to insert in the table since the record already exists in your table. All you need is to update the existing record. Since your table column is a nested table,MULTISET UNION is something which would work in your situation. See below demo:
Tables:
CREATE OR REPLACE TYPE telefoni_nt IS TABLE OF VARCHAR2(100);
CREATE TABLE parlamentari (
cf VARCHAR(16),
nome VARCHAR(20),
cognome VARCHAR(20),
telefoni telefoni_nt
)
NESTED TABLE telefoni STORE AS nested_telefoni;
insert into parlamentari values('1','a','aa',telefoni_nt('VARCHAR(222,444)'));
insert into parlamentari values('2','b','bb',telefoni_nt('VARCHAR(111)'));
Output:
SQL> Select CF ,TELEFONI from parlamentari;
CF TELEFONI
-- ------
1 TELEFONI_NT('VARCHAR(222,444)')
2 TELEFONI_NT('VARCHAR(111)')
Procedure:
CREATE OR REPLACE PROCEDURE copia_telefoni2 (
sorgente IN parlamentari.cf%TYPE,
destinazione IN parlamentari.cf%TYPE
) AS
BEGIN
IF ( sorgente <> destinazione )
THEN
--Using MULTISET Operator to merge the destination column element with the source column elements.
UPDATE parlamentari
SET
telefoni = telefoni MULTISET UNION (SELECT telefoni
FROM parlamentari
WHERE cf = sorgente )
WHERE
cf = destinazione;
ELSE
dbms_output.put_line('Errore! Sorgente e destinazione uguali');
END IF;
COMMIT;
END copia_telefoni2;
/
Execution:
SQL> EXEC copia_telefoni2('1','2');
PL/SQL procedure successfully completed.
Result:
SQL> Select CF ,TELEFONI from parlamentari;
CF TELEFONI
-- ------
1 TELEFONI_NT('VARCHAR(222,444)')
2 TELEFONI_NT('VARCHAR(111)','VARCHAR(222,444)')
So you can see in the result that the two rows now have been merged.

Related

Dynamic Column Update PLSQL trigger

I have two tables A and B
In table A there are two columns "Sequence Number" and "Content".
Name Null? Type
------- ----- ------------
SEQ_NO NUMBER(6)
CONTENT VARCHAR2(20)
In table B there are multiple statement columns like "Stmt_1", "Stmt_2", "Stmt_3" etc.
Name Null? Type
------ ----- ------------
STMT_1 VARCHAR2(20)
STMT_2 VARCHAR2(20)
STMT_3 VARCHAR2(20)
STMT_4 VARCHAR2(20)
I want to create a trigger on table A such that after every insert on table A, according to the "Sequence Number" value the corresponding column in table B gets updated.
For example: If table A has "Sequence Number" = 1 , then "Stmt_1" of table B gets updated to the value of "Content" column in table A.
If table A is given as
"SEQ_NO" "CONTENT"
1 "This is Content"
Then Table B should look like:
"STMT_1","STMT_2","STMT_3","STMT_4"
"This is Content","","",""
My approach is as follows:
create or replace trigger TestTrig
after insert on A for each row
begin
declare
temp varchar2(6);
begin
temp := concat("Stmt_",:new.seq_no);
update B
set temp = :new.content;
end;
end;
But I am getting an error in the update statement.
Does anyone know how to approach this problem?
You need to use dynamic SQL (and ' is for string literals, " is for identifiers):
create or replace trigger TestTrig
after insert on A
for each row
DECLARE
temp varchar2(11);
begin
temp := 'Stmt_' || TO_CHAR(:new.seq_no, 'FM999990');
EXECUTE IMMEDIATE 'UPDATE B SET ' || temp || ' = :1' USING :NEW.content;
end;
/
You probably want to handle errors when seq_no is input as 5 and there is no STMT_5 column in table B:
create or replace trigger TestTrig
after insert on A
for each row
DECLARE
temp varchar2(11);
INVALID_IDENTIFIER EXCEPTION;
PRAGMA EXCEPTION_INIT(INVALID_IDENTIFIER, -904);
begin
temp := 'Stmt_' || TO_CHAR(:new.seq_no, 'FM999990');
EXECUTE IMMEDIATE 'UPDATE B SET ' || temp || ' = :1' USING :NEW.content;
EXCEPTION
WHEN INVALID_IDENTIFIER THEN
NULL;
end;
/
However
I would suggest that you do not want a table B or a trigger to update it and you want a VIEW instead:
CREATE VIEW B (stmt_1, stmt2, stmt3, stmt4) AS
SELECT *
FROM A
PIVOT (
MAX(content)
FOR seq_no IN (
1 AS stmt_1,
2 AS stmt_2,
3 AS stmt_3,
4 AS stmt_4
)
);
fiddle;

SQLite and multiple insert clean

I would like to populate a freshly created Table in a SQLite DB.
In this table, some keys are references to other tables and I'd like not to hard-code these references
-> I'm currently using a "mapping" table in order to fetch ids using names (~ constants emulation)
The problem is: this solution works but is very verbose
Minimal working example: (storing dictionary words, using foreign keys to a category table)
-- Tables creation
CREATE TABLE categories(
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE words(
id INTEGER PRIMARY KEY,
id_category INTEGER NOT NULL,
name TEXT,
FOREIGN KEY(id_category) REFERENCES categories(id)
);
CREATE TABLE CONSTANTS(
name TEXT PRIMARY KEY,
value INTEGER NOT NULL
);
INSERT INTO categories(name) VALUES("noun");
INSERT INTO CONSTANTS(name, value) VALUES("category_noun", last_insert_rowid());
INSERT INTO categories(name) VALUES("abreviation");
INSERT INTO CONSTANTS(name, value) VALUES("category_abreviation", last_insert_rowid());
INSERT INTO categories(name) VALUES("character");
INSERT INTO CONSTANTS(name, value) VALUES("category_character", last_insert_rowid());
And now, the core of the problem: too much verbose.
In this example is only one foreign key, a few insert to illustrate the problem
INSERT INTO words(id_category, name) VALUES
((SELECT value FROM CONSTANTS WHERE name = "category_noun"),
"hello"),
((SELECT value FROM CONSTANTS WHERE name = "category_abreviation"),
"SO"),
((SELECT value FROM CONSTANTS WHERE name = "category_abreviation"),
"user"),
((SELECT value FROM CONSTANTS WHERE name = "category_character"),
"!")
;
I would like to have something looking like this pseudo-sqlite code:
-- same table creations as before
INSERT INTO words(id_category, name) VALUES
-- Fetch constants once
CAT_NOUM = SELECT value FROM CONSTANTS WHERE name = "category_noum"),
CAT_ABREV = SELECT value FROM CONSTANTS WHERE name = "category_abreviation"),
CAT_CHAR = SELECT value FROM CONSTANTS WHERE name = "category_abreviation")
)
-- Fill the table, using constants
(CAT_NOUM, "Hello"),
(CAT_ABREV, "SO"),
(CAT_NOUM, "user"),
(CAT_CHAR, "SO"),
...
;
I'm wondering if
There is already a SQLite solution to this problem
I should use something like sed to replace a hard-coded string like __SED__CAT_NOUM with its greped value in the SQLite script
Doing this stuff programmatically would be the right way
It is better to use INSERT...SELECT with UNION ALL instead of INSERT...VALUES:
INSERT INTO words(id_category, name)
SELECT value, 'hello' FROM CONSTANTS WHERE name = 'category_noun' UNION ALL
SELECT value, 'SO' FROM CONSTANTS WHERE name = 'category_abreviation' UNION ALL
SELECT value, 'user' FROM CONSTANTS WHERE name = 'category_abreviation' UNION ALL
SELECT value, '!' FROM CONSTANTS WHERE name = 'category_character';
See the demo.
Or use Row Values to join to CONSTANTS:
INSERT INTO words(id_category, name)
SELECT c.value, t.column2
FROM CONSTANTS C INNER JOIN (
VALUES ('category_noun', 'hello'),
('category_abreviation', 'SO'),
('category_abreviation', 'user'),
('category_character', '!')
) t ON t.column1 = c.name;
See the demo.
Results:
SELECT * FROM words;
| id | id_category | name |
| --- | ----------- | ----- |
| 1 | 1 | hello |
| 2 | 2 | SO |
| 3 | 2 | user |
| 4 | 3 | ! |

context index with multi columns in oracle return no rows

I create context index with statement:
create TABLE test_context_index
(id number,
column1 VARCHAR2(30),
column2 VARCHAR2(30),
column3 VARCHAR2(30)
)
/
INSERT INTO test_context_index (ID,COLUMN1,COLUMN2,COLUMN3)
VALUES(1,'tinhah','test','test data');
INSERT INTO test_context_index (ID,COLUMN1,COLUMN2,COLUMN3)
VALUES(2,'tinehaeh','test 12','abc');
/
begin
ctx_ddl.create_preference('my_multi', 'MULTI_COLUMN_DATASTORE');
ctx_ddl.set_attribute('my_multi', 'columns', 'column1, column2, column3');
end;
create index myindex on test_context_index(column1)
indextype is ctxsys.context
parameters ('DATASTORE my_multi');
I created successfully.
I run statement return no rows, help me!
Select * from test_context_index where contains (column1, 'h%') > 0;
Try:
Select * from test_context_index where column1 like '%h%';
Put a % at the beginning as well as the end:
Select * from test_context_index where contains (column1, '%h%') > 0;
ID COLUMN1 COLUMN2 COLUMN3
-- ------- ------- -------
1 tinhah test test data
2 tinehaeh test 12 abc
The text index will still be used even with the wildcard at the beginning:
explain plan for Select * from test_context_index where contains (column1, '%h%') > 0;
select * from table(dbms_xplan.display);
Plan hash value: 2922841387
----------------------------------------------------------
| Id | Operation | Name |
----------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| TEST_CONTEXT_INDEX |
| 2 | DOMAIN INDEX | MYINDEX |
----------------------------------------------------------

pl/sql mutation table on update

I know, what this question had been conceived many time, but I need your advice :)
Having 2 table:
Sale_income
| item |income |create_user |create_date |last_update_user | update_time|
|------|-------|------------|-------------------|-----------------|------------|
| 1 | 100 |duck |05-19-2016 |human |05-19-2016 |
| 2 | 250 |dog |05-19-2016 |human |05-19-2016 |
| 3 | 210 |cat |05-20-2016 |human |05-19-2016 |
Sale_income_audit
| item |change_id|last_inc|new_inc |user_update|update_date|operation |
|------|---------|--------|----------|-----------|-----------|------------|
| 1 | 1 |null |05-19-2016|duck |05-19-2016 |I |
| 2 | 2 |null |05-19-2016|dog |05-19-2016 |I |
| 3 | 33 |null |05-20-2016|cat |05-19-2016 |I |
The task is: when somebody doing insert, update or delete, trigger must insert a record above that takes place action into the table Sale_income_audit (type of operation - column 'operation). And at the same time must be update the table
Sale_income (last_update_user and update_time).
I done this so: create package with 3 variables:
create or replace package Sale_income_var as
v_old_income BINARY_INTEGER := null;
v_new_income BINARY_INTEGER := null;
v_item BINARY_INTEGER := null;
END Sale_income_var;
and 2 triggers
first
create or replace trigger audit_income_IUD
after insert or update or delete on Sale_income
for each row
begin
.
.
elsif updating then
Sale_income_var.v_old_income := :old.income;
Sale_income_var.v_new_income := :new.income;
if Sale_income_var.v_item is null then
Sale_income_var.v_item := :old.item;
DBMS_OUTPUT.PUT_LINE(Sale_income_var.v_item);
end if;
.
.
end if;
end audit_income_IUD;
second
create or replace trigger sale_income_au
after update of income on Sale_income
begin
update Sale_income set last_update_user = user, last_update_date = sysdate
where item = Sale_income_var.v_item;
INSERT into Sale_income_audit (item,
change_id,
last_income,
new_income,
user_update,
update_date,
operation)
VALUES (Sale_income_var.v_item,
auto_incr.NEXTVAL,
Sale_income_var.v_old_income,
Sale_income_var.v_new_income,
user,
sysdate,
'U');
Sale_income_var.v_item := null;
end sale_income_au;
It work, but I fell what it is wrong solution. Because updating block from 'general' trigger moved to another trigger and this magic with variables it`s not good to, I'm right?
How would you decide this task and what do you would change in my solution?
Thanks for help :)
It can be done using just one BEFORE trigger.
Before triggers are allowed to modify NEW values of table columns, and these modified values are stored in the table, so last_update_user and update_time columns can be updated in this way.
create or replace trigger audit_income_IUD
BEFORE insert or update or delete on Sale_income
for each row
declare
operation_type char;
begin
if updating or inserting then
:new.update_time := sysdate;
:new.last_update_user := user;
end if;
CASE
WHEN updating THEN operation_type := 'U';
WHEN inserting THEN operation_type := 'I';
WHEN deleting THEN operation_type := 'D';
END CASE;
INSERT into Sale_income_audit (item,
change_id,
last_income,
new_income,
user_update,
update_date,
operation)
VALUES (:old.item,
auto_incr.NEXTVAL,
:old.income,
:new.income,
user,
sysdate,
operation_type);
end;
/
An answer appeared simple. Need use after each row and add in block 'update'
:new.last_update_user := user;
:new.last_update_date := sysdate;
and we can be limited to one trigger with out package!

confused on how to properly use %rowtype for many tables

I'm attempting to create a derived table of country data from several other tables. Those tables look something like this:
Countries
ID | Name
Country_demographics
ID | date | Population | urban_pop | birth_rate
country_financials
ID | date | GDP | GDP_per_capita
Now, I'm trying to make a new table with
New_Table
ID | Name | date | population | urban_pop | birth_rate | gdp | gdp_per_capita
I have a stored procedure that currently looks something like this:
CREATE OR REPLEACE PROCEDURE SP_COUNTRY (
chunkSize IN INT
) AS
--create tables to hold IDs and stats
TYPE idTable IS TABLE OF COUNTRIES.ID%TYPE;
TYPE dateTable IS TABLE OF COUNTRY_DEMOGRAPHICS.EVALUATION_DATE%TYPE;
TYPE totPopTable IS TABLE OF COUNTRY_DEMOGRAPHICS.POPULATION_TOTAL_COUNT%TYPE;
TYPE urbanPopTable IS TABLE OF COUNTRY_DEMOGRAPHICS.POPULATION_URBAN_COUNT%TYPE;
--constructors
ids idTable;
dates dateTable;
totpop totPopTable;
urbanpop urbanPopTable;
--cursors
CURSOR countryCur IS
SELECT c.ID,cd.EVALUATION_DATE,cd.POPULATION_TOTAL_COUNT,cd.POPULATION_URBAN_COUNT
FROM COUNTRIES c,COUNTRY_DEMOGRAPHICS cd
WHERE c.id=cd.COUNTRY_ID
ORDER BY ID,EVALUATION_DATE;
BEGIN
dbms_output.enable(999999);
--open cursor
OPEN countryCur;
LOOP
--fetch and bulk collect
FETCH countryCur BULK COLLECT INTO ids,dates,totpop,urbanpop
LIMIT chunkSize;
--loop over collections
FOR j in ids.FIRST..ids.LAST
LOOP
--populate record
country.COUNTRY_ID := ids(j);
country.EVALUATION_DATE := dates(j);
country.POPULATION_TOTAL_COUNT := totpop(j);
country.POPULATION_URBAN_COUNT := urbanpop(j);
--update/insert table with record (much confusion here on how to update/insert and check if already exists in derived table..)
UPDATE NEW_TABLE SET ROW = country WHERE COUNTRY_ID = ids(j);
dbms_output.put_line('id: ' || country.COUNTRY_ID || ' date: ' || country.EVALUATION_DATE);
dbms_output.put_line(' pop: ' || country.POPULATION_TOTAL_COUNT || ' urban: ' || country.POPULATION_URBAN_COUNT);
END LOOP;
END LOOP;
--close cursor
CLOSE countryCur;
END;
As you can see, I'm using a different table type for each piece of data. I then plan on making a loop and then just inserting/updating in my new_table. I think there must be a better way to do this with %rowtype, or maybe creating a record and inserting the record? I'm not sure
Unless I'm missing something by simplifying this, and assuming cd.date and cf.date are equal, this should work:
INSERT INTO NEW_TABLE (ID, Name, date, population, urban_pop, birth_rate, gdp, gdp_per_capita)
values
(select c.id, c.name, cd.date,
cd.population, cd.urban_pop, cd.birthrate,
cf.gdp, cf.gdp_per_capita)
from Countries c, country_demographics cd, country_financials cf
where c.id = cd.id
and cd.id = cf.id);
Edit: Use the MERGE statement to update or insert depending on if the primary key exists:
MERGE INTO NEW_TABLE nt
USING ( select c.id, c.name, cd.date,
cd.population, cd.urban_pop, cd.birthrate,
cf.gdp, cf.gdp_per_capita
from Countries c, country_demographics cd, country_financials cf
where c.id = cd.id
and cd.id = cf.id ) a
ON (nt.id = a.id )
WHEN MATCHED THEN
UPDATE SET nt.Name = a.Name,
nt.date = a.date,
nt.population = a.population,
nt.urban_pop = a.urban_pop,
nt.birth_rate = a.birth_rate,
nt.gdp = a.gdp,
nt.gdp_per_capita = a.gdp_per_capita
WHEN NOT MATCHED THEN
INSERT (ID, Name, date, population, urban_pop, birth_rate, gdp, gdp_per_capita)
VALUES (a.id, a.Name, a.date, a.population, a.urban_pop, a.birth_rate, a.gdp, a.gdp_per_capita);

Resources