How to prevent Varchar Appending Duplicate Information - sql

I'm learning PL/SQL stored functions in Oracle SQL Developer and I'm having trouble trying to create a function that returns a row that does not contain duplicate information.
Schema information:
APPLICANT which has an ANUMBER (applicant number)
SPOSSESSED (skill possessed) which has an SNAME (skill name) and ANUMBER (to link applicants to skills)
POSITION which has a PNUMBER
SNEEDED which has SNAME and PNUMBER to link required skills to a position
The task essentially is to have a function that takes an anumber and returns a string with the positions available that the applicant has the skills for.
My current code:
CREATE OR REPLACE FUNCTION applicant_position_titles(anum NUMBER) RETURN VARCHAR IS
course_list VARCHAR(300);
first_position POSITION.TITLE%TYPE;
current_position POSITION.TITLE%TYPE;
BEGIN
course_list := '';
FOR spossessed_cursor IN (SELECT sname FROM SPOSSESSED WHERE anumber = anum)
LOOP
FOR sneeded_cursor IN (SELECT PNUMBER FROM SNEEDED WHERE spossessed_cursor.sname = sname)
LOOP
FOR position_cursor IN (SELECT TITLE FROM POSITION WHERE sneeded_cursor.PNUMBER = PNUMBER)
LOOP
course_list := course_list || ' ' || position_cursor.title;
END LOOP;
END LOOP;
END LOOP;
RETURN course_list;
END applicant_position_titles;
/
My Select Statement:
SELECT anumber, applicant_position_titles(anumber)
FROM APPLICANT
WHERE applicant_position_titles(anumber) IS NOT NULL;
The results:
ANUMBER
APPLICANT_POSITION_TITLES(ANUMBER)
1
lecturer lecturer lecturer senior lecturer lecturer professor professor professor
I would like to know how I can optimise this code function to prevent from duplicating positions.
For example for the first row I would like column 2 to have:
ANUMBER
APPLICANT_POSITION_TITLES(ANUMBER)
1
lecture senior lecturer professor
I know that it is happening because each skill can be applied to multiple positions but I don't know what the best way of fixing this issue would be. I've tried a few things such as storing and comparing VARCHARS but nothing seems to be working.
I'm still learning SQL, please go easy on my disgusting code. Thankyou :)

#OldProgrammer is right u can do it in one select statement.
Here is my sample table and data:
create table SPOSSESSED (sname varchar2(30), anumber number);
create table sneeded (sname varchar2(30), pnumber number);
create table "position" (title varchar2(30), pnumber number);
-------------------------------------
insert into SPOSSESSED values('name',1);
insert into SPOSSESSED values('name2',1);
insert into SPOSSESSED values('name3',1);
--------------------------------------
insert into sneeded values ('name',111);
insert into sneeded values ('name2',222);
insert into sneeded values ('name3',222);
--------------------------------------------
insert into "position" values ('lecturer',111);
insert into "position" values ('professor',222);
And here is that one select statement:
select sp.anumber, LISTAGG(p.title,' ') WITHIN GROUP (ORDER BY sp.anumber) AS title
from spossessed sp,sneeded sn,"position" p
where
sp.sname=sn.sname and
p.pnumber=sn.pnumber
group by sp.anumber
Result:
ANUMBER | TITLE
1 lecturer professor
Edit that removes same position title:
select anumber, LISTAGG(title,' ')
WITHIN GROUP (ORDER BY anumber) AS TITLE
from (
select distinct sp.anumber, p.title
from spossessed sp,sneeded sn,"position" p
where sp.sname=sn.sname and p.pnumber=sn.pnumber
)
group by anumber;

Related

Oracle SQL: what is the cleanest way to refactor WITH...SELECT... into a function

So I have been trying to find the best way to re-write a large chunk of SQL in plan script, in form of
WITH
A AS (...<SUB_QA>...),
B AS (...<SUB_QB>...),
C AS (...<SUB_QC>...),
...
SELECT ... FROM
A
LEFT JOIN B
LEFT JOIN C
LEFT JOIN ...
ON ....
into a FUNCTION. This is mainly to facilitate the reuse the same logic represented by that big chunk in multiple places.
Contraint 1: can only use RECORD instead of creating customized TYPE;
Contraint 2: have to keep the content of the those subqueries (e.g.
, etc) under WITH clause as they are, since each is
considerably complex.
So far, I only came up with the following, as a simplified example.
It involves putting the WITH clause in the cursor loop
But does it run the WITH clause in each loop, which would be a big worry in term of performance? When written in function form, and when I run it with SqlDeveloper's 'Explain Plan' function, it doesn't reveal much helpful information at all.
Is there better/cleaner/more performant way to do this?
SQL to create data:
--------PERSON table------------
DROP TABLE Test_Persons;
CREATE TABLE Test_Persons (
PersonID int,
LastName varchar2(255),
FirstName varchar2(255)
);
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(1,'LN_1','FN_1');
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(2,'LN_2','FN_2');
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(3,'LN_21','FN_2');
--------Salary table------------
DROP TABLE TEST_SALARY_A;
CREATE TABLE TEST_SALARY_A ( -- no 'OR REPLACE' for ORACLE
SalaryID int,
PersonID int,
Amount int,
Tax int,
Bank varchar2(20)
);
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(1, 1, 1000, 300, 'BOA1');
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(2, 2, 2000, 600, 'JP1');
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(3, 3, 3000, 900, 'TD1');
--------Address table------------
DROP TABLE TEST_ADDRESS_A;
CREATE TABLE TEST_ADDRESS_A (
AddressID int,
PersonID int,
Address varchar2(255)
);
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(1, 1, 'address1');
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(2, 2, 'address2');
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(3, 3, 'address3');
commit;
Original SQL in Chunk:
------------------Original--------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid WHERE tps.LASTNAME = 'LN_1'
),
TEST_JOINED_2 AS (
SELECT
tps.PERSONID,
tsl.BANK,
tsl.TAX
FROM TEST_PERSONS tps
LEFT JOIN TEST_SALARY_A tsl ON tps.personid = tsl.personid WHERE tps.LASTNAME = 'LN_1'
)
SELECT tj1.PERSONID as tj1_ID, tj1.LASTNAME, tj1.ADDRESS, tj2.PERSONID as tj2_ID, tj2.BANK, tj2.TAX
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_JOINED_2 tj2 ON tj1.PERSONID = tj2.PERSONID
WHERE tj1.LASTNAME = 'LN_1';
Rewrite in FUNCTION:
------------------Rewritten in functions with ------------------
------------------Contraint 1: can only use RECORD instead of creating customized TYPE;------------
------------------Contraint 2: have to keep the content of the two subqueries under WITH clause exactly as it is --------------
CREATE OR REPLACE PACKAGE MY_JOIN_TEST_SP_PACKAGE_3 AS
TYPE join_record_type IS RECORD(
PersonID1 int,
LastName varchar2(255),
Address varchar2(255),
PersonID2 int,
Bank varchar2(20),
Tax int
);
TYPE join_record_table_type IS TABLE OF join_record_type;
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY MY_JOIN_TEST_SP_PACKAGE_3 AS
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED
AS
join_record join_record_type;
BEGIN
FOR x IN (
-------------------start - WITH clause -- does this run for every RECORD x in the loop??? -----------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid WHERE tps.LASTNAME = last_name
),
TEST_JOINED_2 AS (
SELECT
tps.PERSONID,
tsl.BANK,
tsl.TAX
FROM TEST_PERSONS tps
LEFT JOIN TEST_SALARY_A tsl ON tps.personid = tsl.personid WHERE tps.LASTNAME = last_name
)
-------------------end - WITH clause -------------------
-------------------start - main select-----------------------
SELECT tj1.PERSONID as tj1_ID, tj1.LASTNAME, tj1.ADDRESS, tj2.PERSONID as tj2_ID, tj2.BANK, tj2.TAX
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_JOINED_2 tj2 ON tj1.PERSONID = tj2.PERSONID
WHERE tj1.LASTNAME = last_name
-------------------end - main select--------------------------
)
LOOP
SELECT x.tj1_ID, x.LASTNAME, x.ADDRESS, x.tj2_ID, x.BANK, x.TAX
INTO join_record
FROM DUAL;
PIPE ROW (join_record);
END LOOP;
END;
END; -- END of CREATE
/
select * from table(MY_JOIN_TEST_SP_PACKAGE_3.get_joined_data('LN_1'));
EDITED: modified example code to have variable in the WITH clause
----------------------Create GLOBAL TEMPORARY TABLE -------------------------
DROP TABLE my_global_temp_table;
CREATE GLOBAL TEMPORARY TABLE my_global_temp_table (
PersonID int,
LastName varchar2(255),
Address varchar2(255),
Bank varchar2(20)
)
ON COMMIT DELETE ROWS;
----------------------Create PACKAGE AND FUNCTION -------------------------
CREATE OR REPLACE PACKAGE MY_JOIN_TEST_SP_PACKAGE_3 AS
TYPE join_record_type IS RECORD(
PersonID int,
LastName varchar2(255),
Address varchar2(255),
Bank varchar2(20)
);
TYPE join_record_table_type IS TABLE OF join_record_type;
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY MY_JOIN_TEST_SP_PACKAGE_3 AS
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED
AS
join_record join_record_type;
BEGIN
--------------------use GLOBAL TEMPORARY TABLE-------------------------
INSERT INTO my_global_temp_table
-------------------start - WITH ... SELECT ... clause -- does this run for every RECORD x in the loop??? -----------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid
WHERE tps.LASTNAME = last_name
)
SELECT tj1.PERSONID, tj1.LASTNAME, tj1.ADDRESS, ts.BANK
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_SALARY_A ts ON tj1.PERSONID = ts.PERSONID
WHERE tj1.LASTNAME = last_name;
-------------------end - WITH ... SELECT ... clause --
FOR x IN (
SELECT * FROM my_global_temp_table
)
LOOP
SELECT x.PERSONID, x.LASTNAME, x.ADDRESS, x.BANK
INTO join_record
FROM DUAL;
PIPE ROW (join_record);
END LOOP;
END;
END; -- END of CREATE
/
--------------------Call the FUNCTION-------------------------
select * from table(MY_JOIN_TEST_SP_PACKAGE_3.get_joined_data('LN_1'));
EDITED: following #Littefoot suggestion, try out using CREATE GLOBAL TEMP table but giving out '17/21 PL/SQL: ORA-00936: missing expression'. I am not sure why?
EDITED: Corrected the Insert syntax, but will get error 'ORA-14551: cannot perform a DML operation inside a query', I believe that this is because I called the function that contains that Insert from a SELECT
If you are not using variables and other pl sql constructs, I would suggest you break with clauses in the form of a table or materialize view. In this way you don't need to take risk of rewriting the logic of query in pl sql block and missing something.
I would suggest using materialize view advantage of materialize view over the table is that you don't need to drop table next time you load data and also you can use nologging with materialized view.No logging makes it very fast.
It will be very fast and have minimum risk.
Thanks
Bhanu Yadav
As there's nothing dynamic in the WITH factoring clause (i.e. you don't use variables - at least, I didn't notice any), I'd suggest you to create a view (based on that WITH) and use it whenever needed.
If the real query is really complex and takes time to execute, you could create a global temporary table (GTT), most probably choosing to keep its data during the session (ON COMMIT PRESERVE ROWS) properly index it and store view (or WITH's) contents in there. Then you'd use the GTT in your code
Although, Oracle will keep date returned by a query in memory so you might even have to really "execute" it once, but the memory isn't unlimited so ... test it, compare results you get, pick the one that seems to be the best.
To me, the GTT idea sounds promising, but without actual information it is difficult to decide.
[EDIT, about GTT]
Oracle's "Global temporary table" is, actually, "local" from your point if view (note that, if you're on 18c (I don't think you are, though) you can create a private temporary table). You create it once, using the create global temporary table .... Data you insert into it is visible ONLY to you, nobody else; it is restricted to your own transaction (if it is created with the ON COMMIT DELETE ROWS) or session (ON COMMIT PRESERVE ROWS). Pick the one that suits you best.
What does it mean? It means that you'd create the GTT once, providing column list and their datatypes. Every user, that uses your procedure, would insert its own data set into it (you'd use a query with the LAST NAME parameter, as you said) and use it throughout the transaction (or session). Many users can do that at the same time, but - as I've said - everyone would see only its own data.
Here's the pseudocode:
-- create table once. Do NOT create it, drop it, create again tomorrow, drop ...
-- Create it once, use it many times.
create global temporary table gtt_my_data
(id number,
c_name varchar2(20), ...
)
on commit preserve rows;
create index i1_gmd_id on gtt_my_data (id);
-- your procedure
procedure p_myproc (par_last_name in varchar2) is
begin
insert into gtt_my_data (id, c_name, ...)
select id, c_name, ...
from some_table join some_other_table ...
where some_table.last_name = par_last_name;
-- now, do whatever you do. When you need to fetch data from the GTT, do so
select ... into ...
from table_x join gtt_my_data on ...
update ... set some_column = (select another_column
from table_y join gtt_my_data on ...
)
end;
Once you're done: if you end the session, data will be removed from the GTT. If you want, you can do it manually (either delete or truncate its contents).
[EDIT #2: INSERT INTO A GTT]
Insert is wrong; you don't insert values, but something like this:
INSERT INTO my_global_temp_table
WITH test_joined_1 AS
(SELECT tps.personid,
tps.lastname,
tsd.address
FROM test_persons tps
LEFT JOIN test_address_a tsd ON tps.personid = tsd.personid
WHERE tps.lastname = last_name
)
SELECT tj1.personid,
tj1.lastname,
tj1.address,
ts.bank
FROM test_joined_1 tj1
LEFT JOIN test_salary_a ts ON tj1.personid = ts.personid
WHERE tj1.lastname = last_name;
Simplified, on Scott's schema:
SQL> create table test (empno number, deptno number);
Table created.
SQL> insert into test (empno, deptno)
2 with temp as
3 (select empno, deptno from emp)
4 select t.empno, t.deptno
5 from temp t join dept d on d.deptno = t.deptno
6 where d.deptno = 10;
3 rows created.
SQL>

SQL: Stored procedure

I am implementing a library management system in SQL. I have the following table structure and some values inserted in them:
create table books
(
IdBook number(5),
NameBook varchar2(35),
primary key(IdBook)
);
create table users
(
IdUsers number(5),
NameUser varchar2(20),
primary key(IdUsers)
);
create table borrowed
(
IdBorrowed number(5),
IdUsers number(5),
IdBook number(5),
DueDate date,
DateReturned date,
constraint fk_borrowed foreign key(IdUsers) references users(IdUsers),
constraint fk_borrowed2 foreign key(IdBook) references books(IdBook)
);
insert into books values(0,'FairyTale');
insert into books values(1,'Crime and Punishment');
insert into books values(2,'Anna Karenina');
insert into books values(3,'Norwegian Wood');
insert into users values(01,'Robb Dora');
insert into users values(02,'Pop Alina');
insert into users values(03,'Grozavescu Teodor');
insert into users values(04,'Popa Alin');
insert into borrowed values(10,02,3,'22-Jan-2017',null);
insert into borrowed values(11,01,1,'25-Jan-2017','19-Dec-2016');
insert into borrowed values(12,01,3,'22-Jan-2017',null);
insert into borrowed values(13,04,2,'22-Jan-2017','13-Dec-2016');
What I want now is that my db to allow "borrowing" books for the users(i.e insert into the borrowed table) that have no unreturned books(i.e date returned is not null) and if they have unreturned books I want to abandon the whole process. I thought to implement this in the following way:
create or replace procedure borrowBook(IdBorrowed in number,IdUsers number,IdBook number,DueDate date,DateReturned date) as begin
if exists (SELECT u.IdUsers, u.NameUser, b.DateReturned
FROM users u, borrowed b
WHERE u.IDUSERS = b.IdUsers and DateReturned is not null),
insert into borrowed values(IdBorrowed,IdUsers,IdBook,DueDate,DateReturned);
end borrowBook;
The above procedure does not check if the parameter I pass to this function is the same as the one in my select and I do not know how to do this and correctly insert a value in my table.
Any help would be much appreciated. Thank in advance!
You should not name your parameters the same as columns also used inside the procedure.
You can also simplify your procedure to a single INSERT statement, no IF required:
create or replace procedure borrowBook(p_idborrowed in number, p_idusers number, p_idbook number, p_duedate date, p_datereturned date)
as
begin
insert into borrowed (idborrowed, idusers, idbook, duedate, datereturned)
select p_idborrowed, p_idusers, p_idbook, p_duedate, p_datereturned
from dual
where not exists (select *
from users u
join borrowed b on u.idusers = b.idusers
and b.datereturned is not null);
end borrowBook;
It's also good coding style to explicitly list the columns for an INSERT statement. And you should get used to the explicit JOIN operator instead of using implicit joins in the where clause.
What about this one:
create or replace procedure borrowBook( p_IdBorrowed in number ,
p_IdUsers number ,
p_IdBook number ,
p_DueDate date ,
p_DateReturned date )
as
begin
if (SELECT COUNT(*)
FROM borrowed
WHERE IDUSERS = p_IdUsers
AND DateReturned IS NULL) = 0 THEN
insert into borrowed values (p_IdBorrowed ,
p_IdUsers ,
p_IdBook ,
p_DueDate ,
p_DateReturned );
end if ;
end borrowBook;
You would seem to want something like this:
create or replace procedure borrowBook (
in_IdBorrowed in number,
in_IdUsers number,
in_IdBook number,
in_DueDate date,
in_DateReturned date
) as
v_flag number;
begin
select (case when exists (select 1
from borrowed b
where b.IdUsers = in_IdUsers and b.DateReturned is not null
)
then 1 else 0
end)
into v_flag
from dual;
if (flag = 0) then
insert into borrowed
values(in_IdBorrowed, in_IdUsers, in_IdBook, in_DueDate, v_DateReturned);
end if
end -- borrowBook;

Sorting query results

How can I properly sort the results to show the results in the following order
1st - Parent Name
2nd - Child Name
3rd - Teacher Name
4th - Other Name
Where the parent name and child name is on the same table Family
id | parent name | child name
-----------------------------
1 Denz Hanz
2 Denz Pog
3 Joann Mac
while the other names are on different tables teacher table
id | teacher name
-----------------
1 Miguel
2 Sean
and Other_guest table
id | guest name
-----------------
1 Mike
2 Mal
where in cases that the parent did not arrive, the child name will be showed. Result of the query should show something like this
Participant Name
----------------
1. Denz
2. Denz
3. Mac
4. Miguel
5. Sean
6. Mal
7. Mike
I tried using order by field(),order by field asc, field2 dec ... etc but it seems not the result we wanted.
Below query works in most of the databases, this query will first sort by type and then name:
select * from (
select parent_name, 0 sort_by from table1
union all
select chile_name, 1 from table1
union all
select teacher_name, 2 from table2
union all
select guest_name, 3 from table3) t
order by sort_by, parent_name;
Here is my solution for Oracle DB:
Note that the UNION does not allow ORDER BY clause in subsequent select statements, therefore a procedure is necessary that generates the list of guests in the GUESTS table.
The table ARRIVED contains the IDs of the arrived parents, all other tables are self explanatory.
create table Family (id number, parentname varchar2(100), childname varchar2(100));
create table Teacher (id number, teachername varchar2(100));
create table Other_guest (id number, guestname varchar2(100));
create table Arrived (id number);
create table Guests (guestname varchar2(100));
create sequence Family_seq start with 1 order;
create sequence Teacher_seq start with 1 order;
create sequence Other_seq start with 1 order;
create or replace trigger Family_id before insert on Family for each row
begin
select Family_seq.nextval into :new.id from dual;
end;
create or replace trigger Teacher_id before insert on Teacher for each row
begin
select Teacher_seq.nextval into :new.id from dual;
end;
create or replace trigger Other_id before insert on Other_guest for each row
begin
select Other_seq.nextval into :new.id from dual;
end;
insert into Family (parentname, childname) values ('Denz', 'Hanz');
insert into Family (parentname, childname) values ('Denz', 'Pog');
insert into Family (parentname, childname) values ('Joann', 'Mac');
insert into Teacher (teachername) values ('Miguel');
insert into Teacher (teachername) values ('Sean');
insert into Other_guest (guestname) values ('Mike');
insert into Other_guest (guestname) values ('Mal');
insert into Arrived (id) values (1);
insert into Arrived (id) values (2);
create or replace procedure update_guest_list as
pragma autonomous_transaction;
cursor c_parents is select parentname from family where id in (select id from arrived) order by family.parentname;
cursor c_children is select childname from family where id not in (select id from arrived) order by family.childname;
cursor c_teachers is select teachername from teacher order by teacher.teachername asc;
cursor c_others is select guestname from other_guest order by other_guest.guestname asc;
begin
delete from guests;
for parents_rec in c_parents
loop
insert into guests (guestname) values (parents_rec.parentname);
end loop;
for children_rec in c_children
loop
insert into guests (guestname) values (children_rec.childname);
end loop;
for teachers_rec in c_teachers
loop
insert into guests (guestname) values (teachers_rec.teachername);
end loop;
for others_rec in c_others
loop
insert into guests (guestname) values (others_rec.guestname);
end loop;
commit;
end;
/
execute update_guest_list;

Oracle How to assign usernames

How would I assign usernames for students using their initials followed by numbers that increment? I'm not sure how many students I have in my database and I would like their usernames to look like so "AC001, JD003 for example. And also, If the have similar initials I would like the username to continue incrementing in that order. For example "AC001" and "AC002"
You will need to use a trigger on BEFORE INSERT event and a sequence to implement this.
I will assume your table is as follows:
TABLE user
( userid VARCHAR2(100) -- PRIMARY KEY COLUMN
, firstname VARCHAR2(100)
, lastname VARCHAR2(100)
, .... -- Other columns
);
Create a sequence as follows:
CREATE SEQUENCE sq_userid;
You insert rows with userid column set to null.
INSERT INTO user VALUES (null, 'Jack', 'Daniels', ....);
Then, your trigger should look like this:
CREATE OR REPLACE TRIGGER trg_user
BEFORE INSERT ON user
REFERENCING new AS new old AS old
FOR EACH ROW
BEGIN
IF :new.userid IS NULL THEN
:new.userid := UPPER(LEFT(firstname, 1))
|| UPPER(LEFT(lastname, 1))
|| TO_CHAR(sq_userid.nextval, '099');
END IF;
END;
/
The trigger will put 'JD001' AS the value for userid in this instance.
For more information- Coding triggers, sequences and to_char.
i think this will also work
1st Create sequence user_seq;
insert into user values('user_name'||user_seq.nextval);
you can modify above query as per your requirement.
I fiddled a solution, but it's in MySQL. I'm sure you can get the general idea and convert it into Oracle (there's an Oracle 11g option in SQL Fiddle itself): http://sqlfiddle.com/#!2/e8ed5/1
Schema:
CREATE TABLE ALPHA_ROLL (
ALPHA1 CHAR(1),
ALPHA2 CHAR(1),
ROLL INTEGER,
PRIMARY KEY(ALPHA1, ALPHA2)
)//
CREATE TABLE user (
USERID VARCHAR(5),
FIRSTNAME VARCHAR(100),
LASTNAME VARCHAR(100),
PRIMARY KEY (USERID)
)//
CREATE TRIGGER trg_user
BEFORE INSERT ON user
FOR EACH ROW
BEGIN
SET #gen_user_id =
(SELECT CONCAT(ALPHA1,ALPHA2,LPAD((ROLL+1), 3, '0'))
FROM ALPHA_ROLL
WHERE ALPHA1 = UPPER(LEFT(NEW.firstname, 1))
AND ALPHA2 = UPPER(LEFT(NEW.lastname, 1))
LIMIT 1);
IF (#gen_user_id IS NULL) THEN
INSERT INTO ALPHA_ROLL (ALPHA1, ALPHA2, ROLL)
VALUES (UPPER(LEFT(new.firstname, 1)), UPPER(LEFT(new.lastname, 1)), 1);
SET #GEN_USER_ID =
CONCAT(UPPER(LEFT(new.firstname, 1)),
UPPER(LEFT(new.lastname, 1)),
'001');
ELSE
UPDATE ALPHA_ROLL SET ROLL = ROLL + 1
WHERE ALPHA1 = UPPER(LEFT(new.firstname, 1))
AND ALPHA2 = UPPER(LEFT(new.lastname, 1));
END IF;
SET new.userid = #GEN_USER_ID;
END//
SQL:
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Johnnie', 'Walker');
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Jason', 'Mraz');
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Mariah', 'Carrie');
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Justin', 'Morg');
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Matthew', 'Congoman');
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Jack', 'Waltson');
INSERT INTO USER (FIRSTNAME, LASTNAME) VALUES ('Jojo', 'Wonderful');
SELECT * FROM USER;
SELECT * FROM ALPHA_ROLL;
Result:
USERID FIRSTNAME LASTNAME
JM001 Jason Mraz
JM002 Justin Morg
JW001 Johnnie Walker
JW002 Jack Waltson
JW003 Jojo Wonderful
MC001 Mariah Carrie
MC002 Matthew Congoman
ALPHA1 ALPHA2 ROLL
J M 2
J W 3
M C 2

PL/SQL MERGE Using Collection

I am having trouble merging a table with a collection.
Let's say I have a table emp.
Here is my PL/SQL code snippet.
TYPE empcol is table of emp%ROWTYPE INDEX BY BINARY_INTEGER;
tmpemp empcol;
-- Code here to load data from a CSV file into tmpemp collection
-- tmpemp(1).emp_id := parsedstring
-- etc.
MERGE INTO emp A using tmpemp B ON A.emp_id = B.emp_id
WHEN MATCHED THEN UPDATE SET A.fname = B.fname, A.lname = B.lname
WHEN NOT MATCHED THEN INSERT (emp_id, fname, lname) VALUES (b.emp_id, b.fname, b.lname);
Compiler doesn't like it. Its throwing ORA-0942 - Table or View doesn't exist.
What am I doing wrong? or How can I do this better.
Thanks a lot for any help you can provide.
PL/SQL types like emp%ROWTYPE or TABLE OF ... INDEX BY ... cannot be used in SQL queries.
The type must be declared as SQL type (not as PL/SQL type) to be used in SQL query.
Try this approach (example):
create table emp(
firstname varchar2(100),
salary number
);
insert into emp values( 'John', 100 );
commit;
create type my_emp_obj is object(
firstname varchar2(100),
salary number
);
/
create type my_emp_obj_table is table of my_emp_obj;
/
declare
my_emp_tab my_emp_obj_table;
begin
null;
my_emp_tab := my_emp_obj_table( my_emp_obj( 'John', 200 ), my_emp_obj( 'Tom', 300 ));
MERGE INTO emp
USING ( SELECT * FROM TABLE( my_emp_tab )) src
ON ( emp.firstname = src.firstname )
WHEN MATCHED THEN UPDATE SET salary = src.salary
WHEN NOT MATCHED THEN INSERT VALUES( src.firstname, src.salary );
end;
/
select * from emp;
FIRSTNAME SALARY
----------------------- ----------
John 200
Tom 300