Can I use a PL/SQL trigger to check category and concat to a incremental number based on category?

Multi tool use


Can I use a PL/SQL trigger to check category and concat to a incremental number based on category?
I got a productID
- P0001KTC and P0001DR.
productID
If product category is kitchen, I will assign a productID - PROD001KTC
, else if the category is dining room, then the productID
should be PROD001DR
.
productID - PROD001KTC
productID
PROD001DR
Is it possible to write a sequence inside a trigger to check the product category and assign an id as mentioned above?
if there is another living room category product inserted then the id will be PROD001LR.
Which version of Oracle are you on?
– trincot
Jul 31 '16 at 5:42
It's like i am inserting value into product table, i want the product id auto increment eg P0001KTC,P0002LR. It that possible to do that? It will increase the number 0001,0002,0003, after that concatenate with the category as KTC or LR behind of it.
– user3718809
Jul 31 '16 at 5:51
Oracle Database 11g
– user3718809
Jul 31 '16 at 5:52
@WilliamRobertson - Although, re-reading the OP's question, their precise requirement is unclear. I may be about to have wasted the last thirty minutes :(
– APC
Jul 31 '16 at 8:44
2 Answers
2
P0001KTC
is the sort of smart key users love and developers hate. But the customer is king, so here we are.
P0001KTC
The customer's requirement is to increment the numeric element within the product category, so that the same number is used for different categories: P0001KTC , P0001DR , P0002KTC, P0001LR, P0002LR, etc. A monotonically increasing sequence cannot do this.
The best implementation is a code control table, that is a table to manage the assigned numbers. Such an approach entails pessimistic locking, which serializes access to a Product Category (e.g. KTC). Presumably the users won't be creating new Products very often, so the scaling implications aren't severe.
Working PoC
Here's our reference table:
create table product_categories (
product_category_code varchar2(3) not null
, category_description varchar2(30) not null
, constraint product_categories_pk primary key (product_category_code)
)
/
create table product_ids (
product_category_code varchar2(3) not null
, last_number number(38) default 0 not null
, constraint product_ids_pk primary key (product_category_code)
, constraint product_ids_categories_fk foreign key (product_category_code)
references product_categories (product_category_code)
) organization index
/
May these two tables could be one table, but this implementation offers greater flexibility. Let's create our Product Categories:
insert all
into product_categories (product_category_code, category_description)
values (cd, descr)
into product_ids (product_category_code)
values (cd)
select * from
( select 'KTC' as cd, 'Kitchen' as descr from dual union all
select 'LR' as cd, 'Living Room' as descr from dual union all
select 'DR' as cd, 'Dining Room' as descr from dual )
/
Here's the target table:
create table products (
product_id varchar2(10) not null
, product_category_code varchar2(3) not null
, product_description varchar2(30) not null
, constraint products_pk primary key (product_id)
, constraint products_fk foreign key (product_category_code)
references product_categories (product_category_code)
)
/
This function is where the magic happens. The function formats the new Product ID. It does this by taking out a pre-emptive lock on the row for the assigned Category. These locks are retained for the length of the transaction i.e. until the locking session commits or rolls back. So if there are two users creating Kitchen Products one will be left hanging on the other: this is why we generally try to avoid serializing table access in multi-user environments.
create or replace function get_product_id
( p_category_code in product_categories.product_category_code%type)
return products.product_id%type
is
cursor lcur (p_code varchar2)is
select last_number + 1
from product_ids
where product_category_code = p_code
for update of last_number;
next_number product_ids.last_number%type;
return_value products.product_id%type;
begin
open lcur( p_category_code);
fetch lcur into next_number;
if next_number > 999 then
raise_application_error (-20000
, 'No more numbers available for ' || p_category_code);
else
return_value := 'PROD' || lpad(next_number, 3, '0') || p_category_code;
end if;
update product_ids t
set t.last_number = next_number
where current of lcur;
close lcur;
return return_value;
end get_product_id;
/
And here's the trigger:
create or replace trigger products_ins_trg
before insert on products
for each row
begin
:new.product_id := get_product_id (:new.product_category_code);
end;
/
Obviously, we could put the function code in the trigger body but it's good practice to keep business logic out of triggers.
Lastly, here's some test data...
insert into products ( product_category_code, product_description)
values ('KTC', 'Refrigerator')
/
insert into products ( product_category_code, product_description)
values ('DR', 'Dining table')
/
insert into products ( product_category_code, product_description)
values ('KTC', 'Microwave oven')
/
insert into products ( product_category_code, product_description)
values ('DR', 'Dining chair')
/
insert into products ( product_category_code, product_description)
values ('DR', 'Hostess trolley')
/
insert into products ( product_category_code, product_description)
values ('LR', 'Sofa')
/
And, lo!
SQL> select * from products
2 /
PRODUCT_ID PRO PRODUCT_DESCRIPTION
---------- --- ------------------------------
PROD001KTC KTC Refrigerator
PROD001DR DR Dining table
PROD002KTC KTC Microwave oven
PROD002DR DR Dining chair
PROD003DR DR Hostess trolley
PROD001LR LR Sofa
6 rows selected.
SQL>
Note that modelling the smart key as a single column is a bad idea. It is better to build it as a composite key, say unique (product_category, product_number)
, where product_number
is generated from the code control table above. We still need the product_id
for display purposes, but it should be derived from the underlying columns. This is easy using virtual columns, like this:
unique (product_category, product_number)
product_number
product_id
create table products (
product_id varchar2(10)
generated always as 'PROD' || to_char(product_no,'FM003') || product_category_code;
, product_category_code varchar2(3) not null
, product_no number not null
, product_description varchar2(30) not null
, constraint products_pk primary key (product_id)
, constraint products_uk unique (product_category_code, product_no)
, constraint products_fk foreign key (product_category_code)
references product_categories (product_category_code)
)
/
Sorry that my question unclear, i will improve it and thanks for your feedback!
– user3718809
Jul 31 '16 at 9:41
Sorry to ask, my table look like this but not like yours CREATE TABLE ProductCategory( categoryID varchar(15) NOT NULL, description varchar(100) NOT NULL, PRIMARY KEY (categoryID) ); ------ so the way is also same like what you had done?
– user3718809
Jul 31 '16 at 10:05
Yes, the differences are trivial
– APC
Jul 31 '16 at 10:23
Ok, but i only have 2 tables, which are product and productCategory. How do i get the number for productID just like what you done? Appreciate if you could guide me.
– user3718809
Jul 31 '16 at 10:44
You have two choices: add the third table, as I have done, or add a
last_number
to the productCategory
table.– APC
Jul 31 '16 at 11:07
last_number
productCategory
The image shows a different format for productid than what you write in the question. I will assume you want the "PROD" prefix as in the image, and that you can deal with changing those characters in the solution below, if needed.
Also, you write twice the same number (001) in the question, yet in the image, and in the comments you provided, you indicate the numbering should increment always. So this solution will have an always incrementing number.
Proposed Solution
You should store the incremental number separately, and have that as the real id.
The formatted productID could then be a derived column. Since Oracle 11g R1 you can create virtual columns in a table, so you don't really need a trigger for that:
Here is an example script, which creates the table and the sequence:
create table products (
id number not null,
category varchar2(100),
productid as (
'PROD'||
to_char(id, 'FM000') ||
case category when 'Kitchen' then 'KTC'
when 'LivingRoom' then 'LR'
else '???'
end ) virtual,
constraint pk_product_id primary key (id),
);
-- create sequence for inserting incremental id value
create sequence product_seq start with 1 increment by 1;
You insert data like this, without specifying values for the virtual productid column:
-- Insert data
insert into products (id, category) values (product_seq.nextval, 'Kitchen');
insert into products (id, category) values (product_seq.nextval, 'LivingRoom');
And when you select data from the table:
select * from products
You get:
ID | CATEGORY | PRODUCTID
---+------------+-----------
1 | Kitchen | PROD001KTC
2 | LivingRoom | PROD002LR
Note that you'll get into trouble if your id surpasses 999, as then the 3-digit format will not work any more. Oracle will then generate ###
for the to_char
result, so you'll run into duplicate productid values soon.
###
to_char
If you have many more categories than those two (Kitchen & LivingRoom), then you should not extend the earlier mentioned case
statement with those values. Instead you should create a reference table for it (let's call it categories), with values like this:
case
Code | Name
-----+---------------
KTC | Kitchen
LR | Living Room
... | ...
Once you have that table, where Code should be unique, you can just store the code in the products table, not the description:
create table products (
id number not null,
category_code varchar2(10),
productid as (
'PROD'||
to_char(id, 'FM000') ||
category_code) virtual,
constraint pk_product_id primary key (id),
constraint fk_product_category foreign key (category_code)
references catgories(code)
);
You would insert values like this:
insert into products (id, category_code) values (product_seq.nextval, 'KTC');
insert into products (id, category_code) values (product_seq.nextval, 'LR');
And when you want to select data from the table with the category names included:
select product.productid, categories.name
from products
inner join categories on product.category_code = categories.code
This won't produce the IDs specified in the opening line:
P0001KTC
and P0001DR
. Although I agree the OP's requirement is unclear.– APC
Jul 31 '16 at 8:43
P0001KTC
P0001DR
The OP's picture of desired outcome is different from what he writes, but I don't think the difference between "PROD" and "P" is essential to the question or solution...
– trincot
Jul 31 '16 at 8:48
Actually, I was referring to the repeated numeric element: it's
001
in both cases. So is that the requirement or a typo?– APC
Jul 31 '16 at 9:17
001
Indeed unclear, but the OP comments to his own question: ...i want the product id auto increment eg P0001KTC,P0002LR. It that possible to do that? It will increase the number 0001,0002,0003, after that concatenate with the category as KTC or LR behind of it.. This seems to imply he really has a typo in his question, and the numbering should be as in the image he provided.
– trincot
Jul 31 '16 at 9:27
Sorry that my question unclear, i will improve it and thanks for feedback!
– user3718809
Jul 31 '16 at 9:43
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
give at least one example on table.
– Sanjay Radadiya
Jul 31 '16 at 5:25