Tips on how to calculate the age on PL/SQL - oracle

I created an SQL script:
{CREATE TABLE ator (
id NUMBER(3) NOT NULL,
nome_artistico VARCHAR2(25) NOT NULL,
nss NUMBER(11),
sexo CHAR(1),
nacionalidade VARCHAR2(15),
email VARCHAR2(30),
telefone_fixo VARCHAR2(12),
telefone_movel VARCHAR2(12),
dt_nascimento DATE,
CONSTRAINT ator_pk PRIMARY KEY ( id )
);}
My question is: Does anyone has a tip on how to calculate and print the age of x actor? I'm great at SQL but not so good on PL/SQL so if anyone could give a tip I would appreciate it a lot.

You can use months_between as follows:
Select t.*, trunc(months_between(sysdate, dt_nascimento) / 12) as age
From ator t

As you're interested in PL/SQL solution, I'd suggest you to use a function: pass actor's ID to it and return actor's age. A simple example:
SQL> create or replace function f_age (par_id in ator.id%type)
2 return number
3 is
4 l_age number;
5 begin
6 select round(months_between(sysdate, a.dt_nascimento) / 12)
7 into l_age
8 from ator a
9 where a.id = par_id;
10
11 return l_age;
12 end;
13 /
Function created.
SQL> select f_age(5) from dual;
F_AGE(5)
----------
54
SQL>

Related

How to automate sql file execution?

We have CICD in place from long time for our application. Now, we are planning to automate DB script execution as well.
PFB the requirement.
For example, we have create_tables.sql and alter_tables.sql files.
create_tables.sql contains:
CREATE TABLE EMPLOYEE_DETAILS (
EMP_ID VARCHAR2(128 CHAR) NOT NULL,
FIRSTNAME VARCHAR2(128 CHAR),
LASTNAME VARCHAR2(128 CHAR),
CONSTRAINT REQUESTSUBMITTERS_PK PRIMARY KEY ( EMP_ID )
);
alter_tables.sql contains:
--v0.1
ALTER TABLE EMPLOYEE_DETAILS MODIFY FIRSTNAME VARCHAR2(256);
COMMIT;
--v0.2
ALTER TABLE EMPLOYEE_DETAILS MODIFY FIRSTNAME VARCHAR2(256) NOT NULL;
ALTER TABLE EMPLOYEE_DETAILS MODIFY LASTNAME VARCHAR2(256) NOT NULL;
COMMIT;
--v0.3
ALTER TABLE EMPLOYEE_DETAILS ADD EMAIL VARCHAR2(256) NOT NULL;
COMMIT;
Assume that we have an environments ABC. This environment has all changes available in create_tables.sql file but only --v0.1 change from alter_tables.sql file.
Now, we just want to promote JUST --v0.2 change from alter_tables.sql file to this environment. Please note that we don't want to promote changes --v0.1 and --v0.3 from alter_tables.sql file.
So, in general there will be --v0.xxx (a unique incremental indicator) in each sql file to specify which sql statements to execute.
Can you please let me know what could be the way to automate with this approach.
Also, please feel free to suggest any other solution which you think is better than above one.
First of all, ALTER is a DDL and it implicitly commits before and after executing that command, so - explicitly COMMITting is pretty much useless.
From my point of view, your approach is too difficult to apply as you have all the changes stored in the same file so you'd have to parse it and extract only some - desired - parts of it to be executed.
How about creating a set of tables that contains what to apply? Here's an example:
SQL> create table t_change_master
2 (change number constraint pk_cha primary key,
3 change_date date,
4 cb_apply number(1) default 0 not null,
5 applied_date date
6 );
Table created.
SQL> create table t_change_detail
2 (id number constraint pk_chadet primary key,
3 change number constraint fk_chadet_cha references t_change_master (change),
4 command varchar2(200),
5 cb_ok number(1),
6 error varchar2(200)
7 );
Table created.
Some sample data:
SQL> insert into t_change_master (change, change_date, cb_apply)
2 select 1, date '2022-06-25', 1 from dual union all
3 select 2, date '2022-06-26', 0 from dual union all
4 select 3, date '2022-06-28', 0 from dual;
3 rows created.
Note that ID = 1 and 4 contain duplicate commands:
SQL> insert into t_change_detail (id, change, command)
2 select 1, 1, 'alter table test add firstname varchar2(20)' from dual union all
3 select 2, 1, 'alter table test modify firstname varchar2(15)' from dual union all
4 select 3, 2, 'alter table test add lastname varchar2(20)' from dual union all
5 select 4, 2, 'alter table test add firstname varchar2(30)' from dual union all
6 select 5, 3, 'alter table test add address varchar2(30)' from dual;
5 rows created.
SQL>
This is table that will be used in this playground:
SQL> create table test (id number);
Table created.
Procedure that does the job; in two nested loops, it reads rows from the master table which are scheduled to run (cb_apply = 1) but have not been ran yet (applied_date is null). Inner loop just reads what to do, executes it and logs whether it was successful or not.
Basically, you'd schedule its execution using DBMS_SCHEDULER (or DBMS_JOB, depending on your database version):
SQL> create or replace procedure p_change is
2 l_err varchar2(200);
3 begin
4 for cur_m in (select change
5 from t_change_master
6 where cb_apply = 1
7 and applied_date is null
8 )
9 loop
10 for cur_d in (select id, command
11 from t_change_detail
12 where change = cur_m.change
13 )
14 loop
15 begin
16 dbms_output.put_line(cur_d.command);
17 execute immediate cur_d.command;
18
19 update t_change_detail set
20 cb_ok = 1
21 where id = cur_d.id;
22 exception
23 when others then
24 l_err := sqlerrm;
25 update t_change_detail set
26 cb_ok = 0,
27 error = l_err
28 where id = cur_d.id;
29 end;
30 end loop;
31 update t_change_master set
32 applied_date = sysdate
33 where change = cur_m.change;
34 end loop;
35 end;
36 /
Procedure created.
SQL>
OK, let's try it. Setting date format, just to know what is what:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> set serveroutput on;
SQL> begin
2 p_change;
3 end;
4 /
alter table test add firstname varchar2(20)
alter table test modify firstname varchar2(15)
PL/SQL procedure successfully completed.
SQL> select * from t_change_master;
CHANGE CHANGE_DATE CB_APPLY APPLIED_DATE
------- ------------------- ---------- -------------------
1 25.06.2022 00:00:00 1 05.07.2022 17:50:01
2 26.06.2022 00:00:00 0
3 28.06.2022 00:00:00 0
SQL> select * from t_change_detail;
ID CHANGE COMMAND CB_OK ERROR
--- ------- ---------------------------------------------- ------ ----------------------------------------
1 1 alter table test add firstname varchar2(20) 1
2 1 alter table test modify firstname varchar2(15) 1
3 2 alter table test add lastname varchar2(20)
4 2 alter table test add firstname varchar2(30)
5 3 alter table test add address varchar2(30)
SQL>
Let's now run change = 2:
SQL> update t_change_master set cb_apply = 1 where change = 2;
1 row updated.
SQL> begin
2 p_change;
3 end;
4 /
alter table test add lastname varchar2(20)
alter table test add firstname varchar2(30)
PL/SQL procedure successfully completed.
SQL> select * from t_change_master;
CHANGE CHANGE_DATE CB_APPLY APPLIED_DATE
------- ------------------- ---------- -------------------
1 25.06.2022 00:00:00 1 05.07.2022 17:50:01
2 26.06.2022 00:00:00 1 05.07.2022 17:50:58
3 28.06.2022 00:00:00 0
SQL> select * from t_change_detail;
ID CHANGE COMMAND CB_OK ERROR
--- ------- ---------------------------------------------- ------ ----------------------------------------
1 1 alter table test add firstname varchar2(20) 1
2 1 alter table test modify firstname varchar2(15) 1
3 2 alter table test add lastname varchar2(20) 1
4 2 alter table test add firstname varchar2(30) 0 ORA-01430: column being added already
exists in table
5 3 alter table test add address varchar2(30)
SQL>
Right; it kind of works.
Certainly, that piece of code (I wrote in a matter of minutes) could/should be improved, but that's just the general idea.
On the other hand, why wouldn't you do that using version control system, such as Git or Subversion? Good people developed these tools which are much, much more powerful that anything me (or you) could "invent" in such a short time. I guess it's worth to check these products.

Stored procedure with select count(*) and use count in IF statement

I am creating a stored procedure in Oracle database that's resulting in error "ORA-01858: a non-numeric character was found where a numeric was expected".
My procedure is as below:
create or replace procedure testProc(
id IN VARCHAR2,
user IN VARCHAR2,
sender IN VARCHAR2
)
as
vCount number;
begin
select count(*) into vCount from table1 where id='12345'
if vCount=0
insert into table1 (id, user, sender, status) values (id, user, partner, status);
else
update table1 set status='ERR' where id='12345'
end if;
end procedure;
Error: ORA-01858: a non-numeric character was found where a numeric was expected
I tried replacing vCount as int that did not help. Also tried declaring vCount below sender IN VARCHAR2.
Can someone please tell what is correct way to use the above procedure.
Use a MERGE statement then you can do it in a single statement (rather than SELECT followed by either INSERT or UPDATE):
CREATE PROCEDURE testProc(
i_id IN table1.id%TYPE,
i_user IN table1."USER"%TYPE,
i_sender IN table1.sender%TYPE,
i_status IN table1.status%TYPE
)
AS
BEGIN
MERGE INTO table1 dst
USING (
SELECT '12345' AS id
FROM DUAL
) src
ON (src.id = dst.id)
WHEN MATCHED THEN
UPDATE SET status = 'Err'
WHEN NOT MATCHED THEN
INSERT (id, "USER", sender, status)
VALUES (i_id, i_user, i_sender, i_status);
END testProc;
/
db<>fiddle here
This code can't possibly return error you specified because
procedure is invalid (mising statement terminators; column name can't be USER because it is a keyword, reserved for currently logged user)
that error code is related to date issues, while - in your code - there's nothing that looks like a date
Therefore, it is impossible to help you with error you stated. Otherwise, consider NOT naming procedure's parameters the same as column names because that leads to various problems.
Something like this would work, but it is not related to error you got.
Sample table:
SQL> CREATE TABLE table1
2 (
3 id VARCHAR2 (5),
4 c_user VARCHAR2 (20),
5 partner VARCHAR2 (10),
6 sender VARCHAR2 (10),
7 status VARCHAR2 (5)
8 );
Table created.
SQL>
Procedure:
SQL> CREATE OR REPLACE PROCEDURE testProc (p_id IN VARCHAR2,
2 p_user IN VARCHAR2,
3 p_sender IN VARCHAR2)
4 AS
5 vCount NUMBER;
6 BEGIN
7 SELECT COUNT (*)
8 INTO vCount
9 FROM table1
10 WHERE id = p_id;
11
12 IF vCount = 0
13 THEN
14 INSERT INTO table1 (id,
15 c_user,
16 sender,
17 status)
18 VALUES (p_id,
19 p_user,
20 NULL,
21 'NEW');
22 ELSE
23 UPDATE table1
24 SET status = 'ERR'
25 WHERE id = p_id;
26 END IF;
27 END testproc;
28 /
Procedure created.
Testing:
SQL> EXEC testproc('12345', 'Little', 'Foot');
PL/SQL procedure successfully completed.
SQL> SELECT * FROM table1;
ID C_USER PARTNER SENDER STATU
----- -------------------- ---------- ---------- -----
12345 Little NEW
SQL> EXEC testproc('12345', 'Little', 'Foot');
PL/SQL procedure successfully completed.
SQL> SELECT * FROM table1;
ID C_USER PARTNER SENDER STATU
----- -------------------- ---------- ---------- -----
12345 Little ERR
SQL>

PL/SQL: How to insert all records from table type to another table without for loop

I have probably trivial problem but I can't nail the logic quite right.
I have following types:
create or replace TYPE test_rec FORCE
AS OBJECT (ref_id NUMBER (20)
,ref_type VARCHAR2 (4));
create or replace TYPE test_ref_tbl FORCE
AS TABLE OF test_rec;
and actual table
CREATE TABLE my_tbl
( id number(10) NOT NULL,
ref_id varchar2(20) NOT NULL,
ref_type varchar2(4),
CONSTRAINT my_pk PRIMARY KEY (id)
);
Now, in one procedure I get variable test_ref_tbl with data and I have to insert everything to my_tbl, also id should be generated from sequence.
I managed to do this quite easily with for loop
FOR i IN 1 .. test_ref_tbl.COUNT LOOP
INSERT INTO my_tbl(id
,ref_id
,ref_type)
VALUES (my_test_sequence.NEXTVAL
,test_ref_tbl(i).ref_id
,test_ref_tbl(i).ref_type
);
END LOOP;
and everything works fine, but I got alot of flack for inserting data in for loop, I'm not plsql developer so maybe my colleagues are making my job harder just for the hell of it.
But to get back on topic, is there a way to do this without for loop?
Thanks
Yes, there is. Here's an example:
Creating test case first:
SQL> CREATE OR REPLACE TYPE test_rec FORCE AS OBJECT
2 (
3 ref_id NUMBER (20),
4 ref_type VARCHAR2 (4)
5 );
6 /
Type created.
SQL> CREATE OR REPLACE TYPE test_ref_tbl FORCE AS TABLE OF test_rec;
2 /
Type created.
SQL> CREATE TABLE my_tbl
2 (
3 id NUMBER (10) NOT NULL,
4 ref_id VARCHAR2 (20) NOT NULL,
5 ref_type VARCHAR2 (4),
6 CONSTRAINT my_pk PRIMARY KEY (id)
7 );
Table created.
SQL> CREATE SEQUENCE my_test_sequence;
Sequence created.
As data source, I'm using Scott's DEPT table.
SQL> DECLARE
2 l_tab test_ref_tbl;
3 BEGIN
4 SELECT test_rec (deptno, SUBSTR (dname, 1, 4))
5 BULK COLLECT INTO l_tab
6 FROM dept;
7
8 -- this is what you're looking for
9 INSERT INTO my_tbl (id, ref_id, ref_type)
10 SELECT my_test_sequence.NEXTVAL, t.*
11 FROM TABLE (l_tab) t;
12 END;
13 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM my_tbl;
ID REF_ID REF_
---------- -------------------- ----
1 10 ACCO
2 20 RESE
3 30 SALE
4 40 OPER
SQL>

Error raised while creating the trigger in oracle apex

Compilation failed, line 3 (14:38:54) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PLS-00382: expression is of wrong typeCompilation failed, line 3 (14:38:54) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PL/SQL: Statement ignored
CREATE TABLE "P1_EXAM_TIMETABLE"
( "EXAM_ID" NUMBER(7,0),
"SCH_NO" NUMBER(4,0),
"CLASS_CODE" VARCHAR2(3) COLLATE "USING_NLS_COMP",
"EXAM_NAME" VARCHAR2(11) COLLATE "USING_NLS_COMP",
"MAX_MARKS" NUMBER(3,0),
"EXAM_DATE" DATE,
"SCH_SESSION" VARCHAR2(7) COLLATE "USING_NLS_COMP",
"SUBJECT" VARCHAR2(20) COLLATE "USING_NLS_COMP",
"CREATED_BY" VARCHAR2(30) COLLATE "USING_NLS_COMP",
"CREATED_ON" NUMBER,
"UPDATED_BY" VARCHAR2(30) COLLATE "USING_NLS_COMP",
"UPDATED_ON" NUMBER,
CONSTRAINT "P1_EXAM_TIMETABLE_PK" PRIMARY KEY ("EXAM_ID")
USING INDEX ENABLE
) DEFAULT COLLATION "USING_NLS_COMP"
/
CREATE OR REPLACE TRIGGER BI_P1_EXAM_TIMETABLE BEFORE
INSERT ON P1_EXAM_TIMETABLE
FOR EACH ROW
BEGIN
:new.EXAM_ID:= P1_EXAM_TIMETABLE_SEQ.nextval;
:new.CREATED_ON:= SYSDATE;
:new.CREATED_BY:= nvl(v('APP_USER'), 'SYSTEM');
:new.UPDATED_BY:= NULL;
:new.UPDATED_ON:= NULL;
END;
Does Any one know where am i missing, please help me to fix this
Of course, it won't work. This is what you're doing:
Table:
"CREATED_ON" NUMBER, --> number datatype
Trigger:
:new.CREATED_ON:= SYSDATE; --> SYSDATE is a function that returns DATE datatype
You can't put date into a numeric datatype. What to do? Fix column datatype and change it to DATE.
SQL> create table p1_exam_timetable(
2 exam_id number(7,0),
3 sch_no number(4,0),
4 class_code varchar2(3),
5 exam_name varchar2(11),
6 max_marks number(3,0),
7 exam_date date,
8 sch_session varchar2(7),
9 subject varchar2(20),
10 created_by varchar2(30),
11 created_on date, --> Here! Was NUMBER
12 updated_by varchar2(30),
13 updated_on number,
14 constraint p1_exam_timetable_pk primary key(exam_id)
15 using index enable
16 )
17 /
Table created.
SQL> create sequence p1_exam_timetable_seq;
Sequence created.
Trigger:
SQL> create or replace trigger bi_p1_exam_timetable before
2 insert on p1_exam_timetable
3 for each row
4 begin
5 :new.exam_id := p1_exam_timetable_seq.nextval;
6 :new.created_on := sysdate;
7 :new.created_by := nvl(v('APP_USER'), 'SYSTEM');
8 :new.updated_by := null;
9 :new.updated_on := null;
10 end;
11 /
Trigger created.
Testing:
SQL> insert into p1_exam_timetable (exam_id) values (1);
1 row created.
SQL> select * from p1_exam_timetable;
EXAM_ID SCH_NO CLA EXAM_NAME MAX_MARKS EXAM_DATE SCH_SES
---------- ---------- --- ----------- ---------- ---------- -------
SUBJECT CREATED_BY CREATED_ON
-------------------- ------------------------------ ----------
UPDATED_BY UPDATED_ON
------------------------------ ----------
1
SYSTEM 22.02.2021
SQL>
P.S. When working with Oracle, don't use double quotes, they cause nothing but problems.

I have a problem to create function my database in oracle

I have a problem with functions in oracle.
I am trying to add a new row to an existing table but I don't know if I'm going the right direction all the time I have an error.
The function worked in Postgresql and now wants to transform it into Oracle.
Function:
CREATE OR REPLACE FUNCTION add_human(
first_name VARCHAR(15),
last_name VARCHAR (15),
birthday DATE,
pesel BIGINT,
employee_function(VARCHAR(15))
RETURNS void AS
$$ BEGIN
INSERT INTO WORKERS(first_name, last_name, birthday, pesel, employee_function)
VALUES ($1, $2, $3, $4, $5); END; $$ LANGUAGE plpgsql;
Select:
select add_human(‘John’,’Wick’,’1971-01-27’,’71012745639’,’Actor’);
In Oracle, you'd use a procedure to insert rows into a table, not a function (they are to be used to return certain value).
That would be something like this:
SQL> CREATE OR REPLACE PROCEDURE add_human
2 (
3 p_first_name in VARCHAR2,
4 p_last_name in VARCHAR2,
5 p_birthday in DATE,
6 p_pesel in NUMBER,
7 p_employee_function in VARCHAR2
8 )
9 AS
10 BEGIN
11 INSERT INTO WORKERS
12 (first_name, last_name, birthday, pesel, employee_function)
13 VALUES (p_first_name, p_last_name, p_birthday, p_pesel, p_employee_function);
14 END add_human;
15 /
Procedure created.
SQL> BEGIN
2 add_human('John', 'Wick', date '1971-01-27', 71012745639, 'Actor');
3 END;
4 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM workers;
FIRST_NAME LAST_NAME BIRTHDAY PESEL EMPLOYEE_F
---------- ---------- ---------- -------------- ----------
John Wick 1971-01-27 71012745639 Actor
SQL>

Resources