how to implement multivalued attribute in oracle? - sql

I want to store more than one Email IDs in the Email id column of a table, as a multivalued attribute. How can I do this in oracle?

The traditional, relational way of doing this would be with a child heap table:
create table emails
(id number
, email_address varchar2(254)
, constraint em_t23_fk foreign key (id)
references t23 (id)
)
/
However, you are hinting at a nested table:
create type email_t as object
(email_address varchar2(254))
/
create type email_nt as table of email_t
/
alter table t23
add emails email_nt
nested table emails store as emails_table
/
Here's how it works:
SQL> update t23
2 set emails = email_nt (email_t('sam_i_am#example.com')
3 , email_t('green_eggs_n_ham#yahoo.co.uk'))
4 where id = 222
5 /
1 row updated.
SQL> select * from t23
2 where id = 222
3 /
ID NAME DOB
---------- ------------------------------ ---------
EMAILS(EMAIL_ADDRESS)
----------------------------------------------------------------------------------
222 Sam-I-Am 06-AUG-02
EMAIL_NT(EMAIL_T('sam_i_am#example.com'), EMAIL_T('green_eggs_n_ham#yahoo.co.uk'))
SQL>
Edit
The solution with VARRAY is basically the same:
SQL> alter table t23
2 drop column emails
3 /
Table altered.
SQL> create type email_va as varray(5) of varchar2(254)
2 /
Type created.
SQL> alter table t23
2 add emails email_va
3 /
Table altered.
SQL> update t23
2 set emails = email_va ('sam_i_am#example.com'
3 , 'green_eggs_n_ham#yahoo.co.uk')
4 where id = 222
5 /
1 row updated.
SQL> select t23.name
2 , e.*
3 from t23
4 , table (t23.emails) e
5 where t23.id = 222
6 /
NAME COLUMN_VALUE
------------------------------ ---------------------------------
Sam-I-Am sam_i_am#example.com
Sam-I-Am green_eggs_n_ham#yahoo.co.uk
SQL>

The standard way to do this is to define a second table, where you can store one email per row.
Oracle also supports nested tables so a single attribute column can contain multiple values.

Related

How to add new column in a table from existing column in the same table?

I have a table with only 1 column which has several details concatenated into it like Product Name, Shipment Date etc:
MASTERCOLUMN
Row1_Prod1_ShipDate_01-Dec-21
Row2_Prod2_ShipDate_03-Dec-21
Row3_Prod3_ShipDate_07-Dec-21
.
.
The requirement is to add another 2 columns containing only ProductName and ShipmentDate details corresponding to MASTERCOLUMN: (Eg below)
MASTERCOLUMN ProductName ShipmentDate
Row1_Prod1_ShipDate_01-Dec-21 Prod1 01-Dec-21
Row2_Prod2_ShipDate_03-Dec-21 Prod2 03-Dec-21
Row3_Prod3_ShipDate_07-Dec-21 Prod3 07-Dec-21
.
.
.
I've altered the table and added these new columns. Is there any way of updating the table so that corresponding values gets copied to these new columns? Can this be achieved using SQL query? I am using SQL Developer with Oracle 19.c version.
I've tried updating the column after adding the 2 columns..
The below sample query is for updating the ProductName column:
UPDATE TABLE
SET ProductName = (SELECT SUBSTR(MASTERCOLUMN,6,5) FROM TABLE);
But this returns ORA-01427: single-row subquery returns more than one row error.
However, I tried to update only one row and it worked fine;
UPDATE TABLE
SET ProductName = (SELECT SUBSTR(MASTERCOLUMN,6,5) FROM TABLE)
WHERE MASTERCOLUMN = 'Row1_Prod1_ShipDate_01-Dec-21';
But I need to populate the entire table with corresponding values from MASTERCOLUMN column..
Don't use subquery, you don't need it.
Sample table and rows:
SQL> create table test (mastercolumn varchar2(30));
Table created.
SQL> insert into test
2 select 'Row1_Prod1_ShipDate_01-Dec-21' from dual union all
3 select 'Row2_Prod2_ShipDate_03-Dec-21' from dual union all
4 select 'Row3_Prod3_ShipDate_07-Dec-21' from dual;
3 rows created.
Add new columns:
SQL> alter table test add
2 (product_name varchar2(10),
3 shipmentdate date);
Table altered.
Update:
SQL> update test set
2 product_name = substr(mastercolumn,
3 instr(mastercolumn, '_', 1, 1) + 1,
4 instr(mastercolumn, '_', 1, 2) - instr(mastercolumn, '_', 1,1 ) - 1
5 ),
6 shipmentdate = to_date(substr(mastercolumn, -9), 'dd-mon-yy', 'nls_date_language=english');
3 rows updated.
Result:
SQL> select * From test;
MASTERCOLUMN PRODUCT_NA SHIPMENTDA
------------------------------ ---------- ----------
Row1_Prod1_ShipDate_01-Dec-21 Prod1 01.12.2021
Row2_Prod2_ShipDate_03-Dec-21 Prod2 03.12.2021
Row3_Prod3_ShipDate_07-Dec-21 Prod3 07.12.2021
SQL>
But, as you've already been told, you'd rather choose another approach, one of them being a view:
SQL> create or replace view v_test as
2 select mastercolumn,
3 substr(mastercolumn,
4 instr(mastercolumn, '_', 1, 1) + 1,
5 instr(mastercolumn, '_', 1, 2) - instr(mastercolumn, '_', 1,1 ) - 1
6 ) as product_name,
7 to_date(substr(mastercolumn, -9), 'dd-mon-yy', 'nls_date_language=english')
8 as shipmentdate
9 from test;
View created.
SQL> select * From v_test;
MASTERCOLUMN PRODUCT_NAME SHIPMENTDA
------------------------------ -------------------- ----------
Row1_Prod1_ShipDate_01-Dec-21 Prod1 01.12.2021
Row2_Prod2_ShipDate_03-Dec-21 Prod2 03.12.2021
Row3_Prod3_ShipDate_07-Dec-21 Prod3 07.12.2021
SQL>

Update values of one table from other table

For Example I have 2 tables
Table 1:
id number name
-----------------------
1 1684 abc
2 9666 pqr
3 1234 adf
Table 2:
id number name
-----------------------
1 9109 xyz
2 9564 pqr
How do i get output like this?
Table 1:
id number name
-----------------------
1 9109 xyz
2 9564 pqr
3 1234 adf
I want to merge table 2 in table.
Also if the column-"name" value is updated/changed from this merge, I need to update status column and i need to call a procedure of smtp mail, how can i handle this?
The status column has different status transitions(eg: x->y, a->b).
and if the record is not in table1 it should be inserted.
These all operations are inside a procedure used in batch job.
Please advice me with this.
Thank you in advance for help.
There are different ways to do this, say, using MERGE statement, but most simple is
Connected to Oracle Database 12c Enterprise Edition Release 12.1.0.2.0
SQL> create table t$1 ("id" integer, "number" integer, "name" varchar2(3));
Table created
SQL> create table t$2 ("id" integer, "number" integer, "name" varchar2(3));
Table created
SQL> insert into t$1 values (1, 1684, 'abc');
1 row inserted
SQL> insert into t$1 values (2, 9666, 'pqr');
1 row inserted
SQL> insert into t$1 values (3, 1234, 'adf');
1 row inserted
SQL> insert into t$2 values (1, 9109, 'xyz');
1 row inserted
SQL> insert into t$2 values (2, 9564, 'pqr');
1 row inserted
SQL> update t$1 set
2 ("number", "name") = (select "number", "name" from t$2 where t$2."id" = t$1."id")
3 where
4 "id" in (select "id" from t$2);
2 rows updated
SQL> select * from t$1;
id number name
-------- -------- --------
1 9109 xyz
2 9564 pqr
3 1234 adf
You can use MERGE to do it very simply:
MERGE INTO table1 dst
USING table2 src
ON ( src.id = dst.id )
WHEN MATCHED THEN
UPDATE SET number = src.number,
name = src.name;

Insert unique values after addition of a column in a table

We have a table with two columns and have added another column recently (named sequence_no) , Is there a way to insert unique values like , 1,2,3 for every row in the table ?
eg
table name : test
desc test
name varchar2
value varchar2
--> n_seq_no number
select * from test
Name value n_Seq_no
test1 100
test2 200
test3 300
test4 500
The table already had name, and value as the columns of the table, I need to add unique values for the n_Seq_no column with the existing data,
Output format:
select * from test
Name value n_Seq_no
test1 100 1
test2 200 2
test3 300 3
test4 500 4
and so on for all the rows in table.
You could simply set the new column as ROWNUM.
Something like,
SQL> CREATE TABLE t(
2 A NUMBER,
3 b NUMBER);
Table created.
SQL>
SQL> INSERT INTO t(A) VALUES(100);
1 row created.
SQL> INSERT INTO t(A) VALUES(200);
1 row created.
SQL> INSERT INTO t(A) VALUES(300);
1 row created.
SQL>
SQL> SELECT * FROM t;
A B
---------- ----------
100
200
300
SQL>
SQL> UPDATE t SET b = ROWNUM;
3 rows updated.
SQL> SELECT * FROM T;
A B
---------- ----------
100 1
200 2
300 3
SQL>
If you are on 12c, you could use an IDENTITY COLUMN.
Assuming that your table is really big it's better to recreate and repopulate:
rename test to old_test;
create table new_test
as
select t.*, rownum as n_seq_no
from old_test t
order by value;
Don't forget to migrate grants, indexes, triggers and etc if any.
UPDATE: ordering is optional. It is required only if you want to assign n_seq_no value using some predefine ordering.

How to do I update existing records using a conditional clause?

I'm new to Oracle SQL so I have a question .. I have two tables, Table A and Table B .. Now Table A and Table B have the same column names, but in table A, only one column (named 'tracker') actually has data in it .. The rest of the columns in Table A are empty ... What I need to do is update each record in Table A, so that values for other columns are copied over from Table B, with the condition that the the 'tracker' columns value from Table A is matched with the 'tracker' column in Table B ..
Any ideas ?
MERGE INTO tableA a
USING tableB b
ON (a.tracker=b.tracker)
WHEN MATCHED THEN UPDATE SET
a.column1=b.column1,
a.column2=b.column2;
And if exist rows in B that does not exist in A:
MERGE INTO tableA a
USING tableB b
ON (a.tracker=b.tracker)
WHEN MATCHED THEN UPDATE SET
a.column1=b.column1,
a.column2=b.column2
WHEN NOT MATCHED THEN INSERT VALUES
a.tracker,a.column1,a.column2; --all columns
create table a (somedata varchar2(50), tracker number , constraint pk_a primary key (tracker));
create table b (somedata varchar2(50), tracker number, constraint pk_b primary key (tracker));
/
--insert some data
insert into a (somedata, tracker)
select 'data-a-' || level, level
from dual
connect by level < 10;
insert into b (somedata, tracker)
select 'data-b-' || -level, level
from dual
connect by level < 10;
select * from a;
SOMEDATA TRACKER
-------------------------------------------------- -------
data-a-1 1
data-a-2 2
data-a-3 3
data-a-4 4
data-a-5 5
data-a-6 6
data-a-7 7
data-a-8 8
data-a-9 9
select * from b;
SOMEDATA TRACKER
-------------------------------------------------- -------
data-b--1 1
data-b--2 2
data-b--3 3
data-b--4 4
data-b--5 5
data-b--6 6
data-b--7 7
data-b--8 8
data-b--9 9
commit;
update (select a.somedata a_somedata, b.somedata b_somedata
from a
inner join
b
on a.tracker = b.tracker)
set
a_somedata = b_somedata;
select * from a; --see below for results--
--or you can do it this way: (issuing rollback to get data back in previous state)
--for a one column update, either way will work, I would prefer the former in case there is a multi-column update necessary
-- merge *as posted by another person* will also work
update a
set somedata = (select somedata
from b
where a.tracker = b.tracker
);
select * from A; --see below for results--
-- clean up
-- drop table a;
-- drop table b;
this will give you the results:
SOMEDATA TRACKER
-------------------------------------------------- -------
data-b--1 1
data-b--2 2
data-b--3 3
data-b--4 4
data-b--5 5
data-b--6 6
data-b--7 7
data-b--8 8
data-b--9 9
here is a link to oracle's documentation on UPDATE

Inline query to update multiple rows

Here is a brief description of the tables I'm working with in Oracle 10g:
Notes:
Table : jnldetail : Single row with data as shown.
There are multiple package id's attached to the same bill_ref_no for an account. Therefore, I'm trying to update "jnldetail " with the multiple package_id's.
Relation between index_bill_ref and bill_ref_no : 1 - 1
Relation between account_no and ( index_bill_ref and bill_ref_no ) : 1 - Many
**Table : jnldetail** :
account_no bill_ref_no amount
8594822 74282843 822
I'm adding another column package_id with the following command:
alter table jnldetail add package_id number(10)
**table: bill_invoice**:
account_no bill_ref_no index_bill_ref
8594822 74282843 763653495
**table: bill_invoice_detail**:
index_bill_ref package_id component_id
763653495 20000077 20000177
763653495 20000250 20000528
763653495 13000019 13000137
**Expected Result:**
**Table : jnldetail** :
account_no bill_ref_no amount package_id
8594822 74282843 822 20000077
8594822 74282843 822 20000250
8594822 74282843 822 13000019
My Query is:
UPDATE jnldetail tp
SET tp.package_id = (
select
t1.package_id
from bill_invoice_detail t1
, bill_invoice t2
where
t1.index_bill_ref = t2.index_bill_ref
and
t2.account_no = tp.account_no
)
The error message is : ora 01427 : single row subquery returns more than one row
Any inputs will be helpful.
Thanks!
The problem is that you're trying to set tp.package_id to more than one number, because your subquery is returning more than one result, e.g. 20000077 and 13000019. You'll need to alter the subquery so that only one value is returned.
Why not keep the tables separate and use a join when you are ready to get the complete data?
This is tricky for two reasons:
1) you want to update the existing row, and want to add two new rows
2) the two new rows need the data from both the original jnldetail table (amount) and the bill_invoice tables (package_id)
To address 1, you can use the MERGE statement, but because of 2, the jnldetail is needed in the using clause of the MERGE statement.
Here is your example:
SQL> create table jnldetail (account_no, bill_ref_no, amount)
2 as
3 select 8594822, 74282843, 822 from dual
4 /
Tabel is aangemaakt.
SQL> alter table jnldetail add package_id number(10)
2 /
Tabel is gewijzigd.
SQL> create table bill_invoice (account_no, bill_ref_no, index_bill_ref)
2 as
3 select 8594822, 74282843, 763653495 from dual
4 /
Tabel is aangemaakt.
SQL> create table bill_invoice_detail (index_bill_ref, package_id, component_id)
2 as
3 select 763653495, 20000077, 20000177 from dual union all
4 select 763653495, 20000250, 20000528 from dual union all
5 select 763653495, 13000019, 13000137 from dual
6 /
Tabel is aangemaakt.
The tables as you described them.
SQL> UPDATE jnldetail tp
2 SET tp.package_id =
3 ( select t1.package_id
4 from bill_invoice_detail t1
5 , bill_invoice t2
6 where t1.index_bill_ref = t2.index_bill_ref
7 and t2.account_no = tp.account_no
8 )
9 /
( select t1.package_id
*
FOUT in regel 3:
.ORA-01427: single-row subquery returns more than one row
Your update statement fails, because you try to assign the result of a 3-rows-returning-query to a single column.
Here is the MERGE statement:
SQL> merge into jnldetail jd
2 using ( select bi.account_no
3 , bi.bill_ref_no
4 , jd.amount
5 , bid.package_id
6 , row_number() over (partition by bi.account_no,bi.bill_ref_no,bi.index_bill_ref order by null) rn
7 from bill_invoice bi
8 , bill_invoice_detail bid
9 , jnldetail jd
10 where bi.index_bill_ref = bid.index_bill_ref
11 and bi.account_no = jd.account_no
12 and bi.bill_ref_no = jd.bill_ref_no
13 ) bi
14 on ( jd.account_no = bi.account_no
15 and jd.bill_ref_no = bi.bill_ref_no
16 and bi.rn = 1
17 )
18 when matched then
19 update set package_id = package_id
20 when not matched then
21 insert values (bi.account_no,bi.bill_ref_no,bi.amount,bi.package_id)
22 /
3 rijen zijn samengevoegd.
Note that we pick an arbitrary row to be updated: the one with rn = 1.
It leads to the desired result set:
SQL> select * from jnldetail
2 /
ACCOUNT_NO BILL_REF_NO AMOUNT PACKAGE_ID
---------- ----------- ---------- ----------
8594822 74282843 822 13000019
8594822 74282843 822 20000077
8594822 74282843 822 20000250
3 rijen zijn geselecteerd.
Regards,
Rob.