Generating unique value and a virtual column - oracle

In an attempt to generate random unique values, which can be exposed (ie customer_id) and used as a PRIMARY KEY I am doing the following:
Generate random number, grabbing the seconds and fractions of a second from the current timestamp, and also I append to that a fixed width sequence to guarantee the uniqueness. This is all working fine. See below as I am generating some customer information.
My question is how can I make the seed column virtual as I really don't need to have it stored but I would like to see the value if needed.
Thanks in advance to all who answer and apologies for the verbose test CASE
CREATE OR REPLACE PACKAGE mf_names IS
FUNCTION random_first_name(
gender IN VARCHAR2 DEFAULT NULL,
percentage_mf IN NUMBER DEFAULT 50
) RETURN VARCHAR2;
FUNCTION random_last_name RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY mf_names IS
first_names_male SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Tom', 'Andy', 'Paul', 'Peter', 'Keith', 'Mark', 'Solomon', 'Joseph', 'John', 'Roger', 'Douglas','Harry', 'Barry', 'Larry', 'Gary', 'Jeffrey', 'David', 'Stuart', 'Karl', 'Seth', 'David', 'Brian', 'Sidney', 'James', 'Shane', 'Zachary', 'Anthony'
);
first_names_female SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Alice', 'Anna', 'Lee', 'Barbara', 'Carol', 'Debra', 'Madison', 'Faith', 'Cheryl', 'Beth', 'Kathy', 'Abigail', 'Jill', 'Grayce', 'Lynn', 'Roz', 'Carolyn', 'Deena', 'Laura', 'Sophia', 'Elise'
);
last_names SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Cooper', 'Dimeo', 'Caygle', 'Luppo', 'Coralnick', 'Torchiano', 'Fazio', 'Behrens', 'Zaza', 'Lebowitz', 'Stern', 'Malden', 'Kramer', 'Stein', 'Tessio', 'Weinreb', 'Dillon', 'Zanona', 'Rucker', 'Zanzone', 'Santoro', 'Barese', 'Silverberg', 'Aarron', 'Kern', 'Saladino', 'Rice', 'Sanford', 'Orr', 'Roth'
);
FUNCTION random_first_name(
gender IN VARCHAR2 DEFAULT NULL,
percentage_mf IN NUMBER DEFAULT 50
) RETURN VARCHAR2
IS
BEGIN
IF UPPER(gender) LIKE 'M%' THEN
RETURN first_names_male(FLOOR(DBMS_RANDOM.VALUE(1, first_names_male.COUNT + 1)));
ELSIF UPPER(gender) LIKE 'F%' THEN
RETURN first_names_female(FLOOR(DBMS_RANDOM.VALUE(1, first_names_female.COUNT + 1)));
ELSIF DBMS_RANDOM.VALUE(0, 100) < percentage_mf THEN
RETURN random_first_name('M');
ELSE
RETURN random_first_name('F');
END IF;
END;
FUNCTION random_last_name RETURN VARCHAR2
IS
BEGIN
RETURN last_names(FLOOR(DBMS_RANDOM.VALUE(1, last_names.COUNT + 1)));
END;
END;
/
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
seed NUMBER,
first_name VARCHAR2 (20),
last_name VARCHAR2 (20),
constraint customer_id_pk primary key (customer_id));
create sequence customer_seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
/
create or replace function dec34(p_str varchar2) return number is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := 0;
begin
for i in 1 .. length(p_str) loop
l_num := l_num * 34 + instr(l_dig,upper(substr(p_str,i,1)))-1;
end loop;
return l_num;
end;
/
create or replace trigger customer_trg
before update on customers for each row
begin
if ( updating('customer_id') )
then
raise_application_error(-20000,'Cant Update customer_id');
end if;
if ( updating('seed') )
then
raise_application_error(-20001,'Cant Update seed');
end if;
end;
/
DECLARE
seed NUMBER;
begin
for i in 1 .. 100 loop
seed := (to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||customer_seq.nextval));
INSERT into customers(
customer_id,
seed,
first_name,
last_name
) VALUES (
base34(seed),
seed,
mf_names.random_first_name(),
mf_names.random_last_name()
);
end loop;
end;
/
select to_char(seed) from customers where rownum = 1
union all
select base34(seed) from customers where rownum = 1
union all
select to_char(dec34(base34(seed))) from customers where rownum = 1;
TO_CHAR(SEED)
5444355405000301000000
BZCK24C0D3CHYE6
5444355405000301000000
select base34(seed),
dump(base34(seed)) from customers where rownum = 1
BASE34(SEED) DUMP(BASE34(SEED))
BZCK24C0D3CHYE6 Typ=1 Len=15: 66,90,67,75,50,52,67,48,68,51,67,72,89,69,54

I'm afraid you can't do that because dbms_random.value and systimestamp functions you'd want to use aren't deterministic (they should return the same value whenever called, while these two don't).
For example: this works because 1 + 2 is always 3:
SQL> create table test
2 (id number generated always as identity,
3 seed number generated always as (1 + 2)
4 );
Table created.
SQL> drop table test;
Table dropped.
Can you use dbms_random.value? Nope:
SQL> create table test
2 (id number generated always as identity,
3 seed number generated always as (to_number(trunc(dbms_random.value(1000,9999))))
4 );
seed number generated always as (to_number(trunc(dbms_random.value(1000,9999))))
*
ERROR at line 3:
ORA-30553: The function is not deterministic
If the function is deterministic, mark it DETERMINISTIC. If it is not deterministic (it depends on package state, database state, current time, or anything other than the function inputs) then do not create the index. The values returned by a deterministic function should not change even when the function is rewritten or recompiled.
Can you use sysdate (or systimestamp, no difference)? No:
SQL> create table test
2 (id number generated always as identity,
3 seed number generated always as (to_number(to_char(sysdate, 'yyyymmdd')))
4 );
seed number generated always as (to_number(to_char(sysdate, 'yyyymmdd')))
*
ERROR at line 3:
ORA-54002: only pure functions can be specified in a virtual column expression
Column expression contained a function whose evaluation is non-deterministic
Therefore, that won't work.

Wouldn't it be simpler to just use SYS_GUID function to generate the primary key value ? It is a unique number. We do this for all the tables in our projects.
CREATE TABLE some_name
( some_name_id NUMBER DEFAULT ON NULL to_number(sys_guid(), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') NOT NULL
PRIMARY KEY
--other columns
) ;
No function needed, no seeding needed, no virtual column needed.

Related

Oracle converting a function to a trigger

I have the following function, which works,that I would like to convert into a INSERT/UPDATE
trigger for the column hash_pk. I'm struggling with syntax errors trying to convert this to a trigger. Could someone please help me out.
Secondly, would it be more efficient to store the column hash_pk as a RAW(if so how big) instead of a VARCHAR2?
Thanks in advance to all that answer.
CREATE or REPLACE FUNCTION HASH_SHA512 (
psINPUT IN VARCHAR2
) RETURN VARCHAR2 AS
rHash RAW (512);
BEGIN
rHash := DBMS_CRYPTO.HASH (TO_CLOB (psINPUT),
dbms_crypto.HASH_SH512);
RETURN ((RAWTOHEX (rHash)));
END HASH_SHA512;
/
CREATE table t(
seq_num integer GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
hash_pk VARCHAR2(1000) not NULL PRIMARY KEY,
c CLOB,
create_date DATE DEFAULT SYSDATE
);
/
create or replace
trigger hash_trg
before insert or update on t
for each row
begin
:new.hash_pk := HASH_SHA512(:new.c);
end;
insert into t (c) values (
rpad('z',16,'z')
);
SELECT * from t
SEQ_NUM HASH_PK C CREATE_DATE
1 2C9437F9D8FB13FC959CA2B9D5B81958B5A32556C60E35D66D1DA92227593A14316FD32EE2B3EEE06EECB1484A0CACAE61A4F930E772BB78AC84E75948DAA628 zzzzzzzzzzzzzzzz 12-OCT-21
update t set c='Good Bye';
SELECT * from t;
SEQ_NUM HASH_PK C CREATE_DATE
1 DCBC14FA2F46F1E264BBD52C4A3DF87E32CC511B43FD9AD722EACCFCA6D8CBE398D10E61E83A85625C7CF96E70348F2D33595196577B01C488030E560A7D34F7 Good Bye 12-OCT-21
I got it was missing parenthesis
create or replace
trigger hash_trg
before insert or update on t
for each row
begin
:new.hash_pk := DBMS_CRYPTO.HASH ((:new.c),
dbms_crypto.HASH_SH512);
end;

how to look up another field in Oracle Function

Table 1
ID
----------
1
2
3
4
5
Table 2
ID Desc
------------------------------
A1 Apple
A2 Pear
A3 Orange
I am trying to create a Function in Oracle, so that it add the prefix 'A' in Table 1, and after that I want to look up in Table 2 to get the DESC returned. It has to be a function.
Thank you!!!
You may use the following for creation of such a function :
Create or Replace Function Get_Fruit( i_id table2.description%type )
Return table2.description%type Is
o_desc table2.description%type;
Begin
for c in ( select description from table2 where id = 'A'||to_char(i_id) )
loop
o_desc := c.description;
end loop;
return o_desc;
End;
where
no need to include exception handling, because of using cursor
instead of select into clause.
using table_name.col_name%type for declaration of data types for
arguments or variables makes the related data type of the columns
dynamic. i.e. those would be able to depend on the data type of the
related columns.
the reserved keywords such as desc can not be used as column names
of tables, unless they're expressed in double quotes ("desc")
To call that function, the following might be preferred :
SQL> set serveroutput on
SQL> declare
2 i_id pls_integer := 1;
3 o_fruit varchar2(55);
4 begin
5 o_fruit := get_fruit( i_id );
6 dbms_output.put_line( o_fruit );
7 end;
8 /
Apple
PL/SQL procedure successfully completed
I am not sure with your question- Are you trying to achieve something like this:-
CREATE OR REPLACE FUNCTION Replace_Value
(
input_ID IN VARCHAR2
) RETURN VARCHAR2
AS
v_ID varchar(2);
BEGIN
begin
SELECT distinct a.ID into v_id from Table 2 a where a.ID in (select 'A'||b.id from table1 b where b.id=input_ID);
exception
when others then
dbms_output.put_line(sqlcode);
end;
RETURN v_id;
END Replace_Value;
Are you trying for something like this?
CREATE OR replace FUNCTION replace_value (table_name IN VARCHAR2,
input_id IN INTEGER)
RETURN VARCHAR2
AS
v_desc VARCHAR(20);
BEGIN
SELECT descr
INTO v_desc
FROM table2
WHERE id = 'A' || input_id
AND ROWNUM = 1; -- only needed if there are multiple rows for each id.
RETURN v_desc;
END replace_value;
You may also add an exception handling for NO_DATA_FOUND or INVALID_NUMBER

Returning multiple values from a function in oracle

I have the following table:
T_TYPE_ID T_TYPE T_TYPE_PRICE T_TYPE_START_DATE T_TYPE_END_DATE
1 student 10.95 01.04.2015 00:00:00 30.06.2015 00:00:00
2 Concession 5.5 01.04.2015 00:00:00 30.06.2015 00:00:00
I need to:
Create FUNC_get_ t_type_end_date function
This function should contain the following input parameter: t_type_p and the following output parameters: t_type_price_p and t_type_end_date_p. It should return 1 if having a record with t_type as t_type_p otherwise return 0. In addition, in the case of having a record, it should assign the latest t_type_end_date to t_type_end_date_p and t_type_price to t_type_price_p. Please note that t_type_end_date_p can be null; it means that the associated price is currently valid.
I have written the following code:
CREATE OR REPLACE FUNCTION FUNC_get_t_type_end_date
( t_type_p IN VARCHAR2)
RETURN NUMBER
AS
cnum NUMBER;
CURSOR cr1 IS
SELECT t_type
FROM ticket_type
WHERE t_type = t_type_p;
BEGIN
OPEN cr1;
FETCH cr1 INTO cnum;
IF cr1%NOTFOUND THEN
cnum := 0;
END IF;
CLOSE cr1;
RETURN cnum;
END;
I did not get any clue on how to return multiple values from a function. I am using oracle.
Hint:
create or replace FUNCTION FUNC_get_ t_type_end_date (…)
return number
as
-- define a variable to return a number and assign 0 to it
-- define a cursor to obtain t_type_price, t_type_end_date of the given t_type_p. The t_type_end_date values should be sorted in descending order – to do so the first record will contain either null or the latest of t_type_end_date
BEGIN
-- open cursor
-- fetch the first record from the cursor to t_type_price_p and t_type_end_date_p
-- if (having a record) then …
-- close cursor
RETURN …
END;
It is not possible to return more than one variable from function. However, it is possible to return a customized variable (i.e. record) type that contains multiple values. To do this, you need first to define type contains the three variables you want to return as follows:
TYPE new_type is record(cnum number, t_type_end_date_p timestamp, t_type_price_p timestamp);
Then you can use it in your function as follows:
CREATE OR REPLACE FUNCTION FUNC_get_t_type_end_date ( t_type_p IN VARCHAR2)
RETURN new_type AS
new_type_variable newtype;
CURSOR cr1 IS
SELECT t_type
FROM ticket_type
WHERE t_type = t_type_p;
BEGIN
OPEN cr1;
FETCH cr1 INTO cnum;
IF cr1%NOTFOUND THEN
SELECT 0, null, null into new_type_variable from dual;
ELSE
SELECT 1, cr1.t_type_end_date, cr1.t_type_price into new_type_variable from dual;
END IF;
CLOSE cr1;
RETURN new_type_variable ;
END;
Try this:
create or replace function func_get_t_type_end_date(t_type_p in varchar2, t_type_price_p out number, t_type_end_date_p out date) return number
as
cnum NUMBER;
CURSOR cr1 IS
SELECT t_type_price,t_type_end_date
FROM ticket_type t
WHERE t_type = t_type_p
and not exists (select 1
from ticket_type t2
where t2.t_type = t.t_type
and t2.t_type_end_date>t.t_type_end_date);
BEGIN
OPEN cr1;
FETCH cr1 INTO t_type_price_p,t_type_end_date_p;
IF cr1%NOTFOUND THEN
cnum := 0;
else
cnum := 1;
END IF;
CLOSE cr1;
RETURN cnum; end;
Example of using:
set serveroutput on
declare
v_result number;
v_type_price_p number;
v_type_end_date_p date;
begin
v_result:=func_get_t_type_end_date(t_type_p=>'student',t_type_price_p=>v_type_price_p,t_type_end_date_p=>v_type_end_date_p);
dbms_output.put_line('Result: '||v_result);
dbms_output.put_line('Price: '||v_type_price_p);
dbms_output.put_line('End date: '||v_type_end_date_p);
end;
/

Updating table in SQLPLUS (Stored Procedure Loop with Comma Delimited Column)

Having some trouble writing my stored procedure. Using Oracle 11g
Goal: I want to be able to create separate rows in my table "info_table" from my table "places_table" with the column alternatenames. Under the column alternatenames from places_table, there is a comma delimited string with multiple alternate names. I want to create a row for each one of these alternate names in table "info_table".
ex of alternatenames column string:
Beijing,Beijingzi,Pei-ching-tzu
what I am hoping to achieve
ID Name
100000000 Beijing
100000001 Beijingzi
100000002 Pei-ching-tzu
Currently my code looks like this:
CREATE TABLE INFO_TABLE
(
INFOID NUMBER PRIMARY KEY,
NAME VARCHAR2(500),
LANGUAGE VARCHAR2(40),
STATUS VARCHAR2(50),
COUNTRY_CODE CHAR (10),
COUNTRY_CODE_2 CHAR (10),
GID CHAR(10),
SUPPLIERID CHAR(10),
LAST_MODIFIED CHAR(50)
);
CREATE SEQUENCE INFO_COUNTER
START WITH 100000000;
CREATE PROCEDURE LOAD_ALTERNATE_NAMES(ALTERNATENAMES_COLUMN VARCHAR2)
AS
COMMA_FINDER NUMBER := 1;
BEGIN
IF ALTERNATENAMES_COLUMN IS NOT NULL
THEN
<<SEPARATE_ALTERNATENAMES>> WHILE COMMA_FINDER!=0 LOOP
INSERT INTO INFO_TABLE
(INFOID, NAME, LANGUAGE, STATUS, COUNTRY_CODE, COUNTRY_CODE_2, GID, SUPPLIERID, LAST_MODIFIED)
VALUES
(INFO_COUNTER, SUBSTR(ALTERNATENAMES_COLUMN, INSTR(P.ALTERNATENAMES, ',', COMMA_FINDER+1)), NULL, 'ALTERNATE', P.COUNTRY_CODE, P.COUNTRY_CODE_2, P.GID, NULL, P.LASTMODIFIED)
FROM INFO_TABLE I, PLACES_TABLE P;
COMMA_FINDER := INSTR(ALTERNATENAMES, ',', COMMA_FINDER);
END LOOP SEPARATE_ALTERNATENAMES;
COMMA_FINDER:=1;
ENDIF;
END
/
LOAD_ALTERNATE_NAMES(SELECT ALTERNATENAMES FROM PLACES_TABLE);
currently the problem is that my INSERT statement in my loop is giving me "SQL Statement Ignored" and I am not sure why. I have taken a look at the stored procedure and loop documentation but can't figure out if I am doing something wrong or there is a typo.
can someone help me please?
Thank you in advance,
Norman
The INSERT statement has either the form:
INSERT INTO table (...) VALUES (...)
or:
INSERT INTO table (...) SELECT ... FROM ...
That's why Oracle issues an error message.
But there's more. You pass the ALTERNATENAMES string value to the stored procedure but need more data from the PLACES_TABLE. Furthermore, Oracle doesn't support stored procedure calls like this:
LOAD_ALTERNATE_NAMES(SELECT ALTERNATENAMES FROM PLACES_TABLE);
So I propose you create a stored procedure without parameters:
CREATE PROCEDURE LOAD_ALTERNATE_NAMES
AS
COMMA_FINDER NUMBER;
BEGIN
FOR REC IN (
SELECT * FROM PLACES_TABLE WHERE ALTERNATENAMES IS NOT NULL
) LOOP
COMMA_FINDER NUMBER := 1;
<<SEPARATE_ALTERNATENAMES>> WHILE COMMA_FINDER!=0 LOOP
INSERT INTO INFO_TABLE
(INFOID, NAME, LANGUAGE, STATUS, COUNTRY_CODE, COUNTRY_CODE_2, GID, SUPPLIERID, LAST_MODIFIED)
VALUES
(INFO_COUNTER.NEXTVAL, SUBSTR(REC.ALTERNATENAMES, INSTR(REC.ALTERNATENAMES, ',', COMMA_FINDER+1)), NULL, 'ALTERNATE', REC.COUNTRY_CODE, REC.COUNTRY_CODE_2, REC.GID, NULL, REC.LASTMODIFIED);
COMMA_FINDER := INSTR(REC.ALTERNATENAMES, ',', COMMA_FINDER);
END LOOP SEPARATE_ALTERNATENAMES;
END LOOP;
END
/
I hope that helps you proceed. I haven't test it and I'm afraid that SUBSTR will fail once it reaches the last name. But you'll figure that out.
Here is a little function I use to loop things like you are asking for. You can specify a delimiter.
The type...
type split_array is table of varchar2(32767) index by binary_integer;
The function...
function split(string_in varchar2, delim_in varchar2) return split_array is
i number :=0;
pos number :=0;
lv_str varchar2(32767) := string_in;
strings split_array;
dl number;
begin
-- determine first chuck of string
pos := instr(lv_str,delim_in,1,1);
-- get the length of the delimiter
dl := length(delim_in);
if (pos = 0) then --then we assume there is only 1 items in the list. so we just add the delimiter to the end which would make the pos length+1;
strings(1) := lv_str;
end if;
-- while there are chunks left, loop
while ( pos != 0) loop
-- increment counter
i := i + 1;
-- create array element for chuck of string
strings(i) := substr(lv_str,1,pos-1);
-- remove chunk from string
lv_str := substr(lv_str,pos+dl,length(lv_str));
-- determine next chunk
pos := instr(lv_str,delim_in,1,1);
-- no last chunk, add to array
if pos = 0 then
strings(i+1) := lv_str;
end if;
end loop;
-- return array
return strings;
end split;
How to use it...
declare
/* alternatenames varchar2(32767) := 'one,two,three,four'; */
nameArray split_array;
begin
for c1 in ( select alternatenames from yourTable where alternatenames is not null )
loop
nameArray := split(c1.alternatenames,',');
for i in 1..nameArray.count loop
/* dbms_output.put_line(nameArray(i)); */
insert into yourTable ( yourColumn ) values ( nameArray(i) );
end loop;
end loop;
end;
/

Return 2 values from a PL-SQL function

How can i return 2 values from a PL-SQL function?
I would not advocate creating a function with an OUT parameter for the second value, because I like to think of functions as a pure concept: a function performs an operation on one or more inputs to produce one output. It shouldn't change any of its arguments or have any other "side effects".
So if you need two outputs, write a procedure instead:
procedure get_sqrt_and_half
( p_input number
, p_sqrt OUT number
, p_half OUT number
)
is
begin
p_sqrt := sqrt(p_input);
p_half := p_input/2;
end;
A function can only return a single SQL type, but that can be a user-defined type with multiple values. I'd need to know more about the actual end requirements before I'd recommend this as a solution, but it is a possibility.
create or replace type a_b is object (a number, b number);
/
create or replace function ret_a_b return a_b is
begin
return a_b(1,2);
end;
/
select ret_a_b from dual;
select d.rab.a, d.rab.b from (select ret_a_b rab from dual) d;
You can return one value directly and another one as an OUT parameter. Or you return a record that contains both values. The first option is, in most cases, simpler to do.
**If you are wanting to use it in SQL, then you would need a pipelined function e.g.**
CREATE OR REPLACE TYPE myemp AS OBJECT
( empno number,
ename varchar2(10),
job varchar2(10),
mgr number,
hiredate date,
sal number,
comm number,
deptno number
);
CREATE OR REPLACE TYPE myrectable AS TABLE OF myemp ;
enter code here
CREATE OR REPLACE FUNCTION pipedata(p_min_row number, p_max_row number) RETURN myrectable PIPELINED IS
v_obj myemp := myemp(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
BEGIN
FOR e IN (select *
from (
select e.*
,rownum rn
from (select * from emp order by empno) e
)
where rn between p_min_row and p_max_row)
LOOP
v_obj.empno := e.empno;
v_obj.ename := e.ename;
v_obj.job := e.job;
v_obj.mgr := e.mgr;
v_obj.hiredate := e.hiredate;
v_obj.sal := e.sal;
v_obj.comm := e.comm;
v_obj.deptno := e.deptno;
PIPE ROW (v_obj);
END LOOP;
RETURN;
END;
SQL> select * from table(pipedata(1,5));
Try using OUT parameters:
create or replace function f(a IN NUMBER, b OUT NUMBER) RETURN NUMBER IS
BEGIN
b := a;
RETURN a;
END f;

Resources