create correct procedure - plsql

I'm trying to write a simple PL/SQL procedure, but I keep getting errors I don't understand. there are some
syntax failures, and I would appreciate if someone could help me with them.
I need to create a procedure that will allow me insert in a table particular person(varchar2) with two dates(DATE), his contract duration.
However his contract CAN'T be between existing dates, thus can't overlap, same person can have number of contacts but only between completely different periods.
I understand that I need to use INTERSECT, it's just I'm confused how to use it here, since I we have only one table. I've intersection before with two tables, how do I intersect it here? I;m also not sure if I can have 2 if statements
Thank you!
CREATE OR REPLACE PROCEDURE test1
(name VARCHAR2,
startDate DATE,
endDate DATE)
AS
overlap NUMBER := 0;
CURSOR cursor IS
SELECT date_from, date_to FROM contractTable;
BEGIN
FOR row IN cursor LOOP
/*check if there is an overlap between dates*/
IF (startDate, endDate) INTERESECT (SELECT date_from, date_to FROM ContractTable) THEN
overlap := overlap + 1;
END IF;
/*if there isn't, then insert new contact for this person*/
IF overlap <= 0 THEN
INSERT INTO ContractTable VALUES(name, startDate, endDate);
END IF;
END test1;

CREATE PROCEDURE test1 (l_name IN VARCHAR2, l_start_date IN DATE, l_end_date IN DATE)
IS
l_overlap_check NUMBER;
BEGIN
SELECT count(*) INTO l_overlap_check
FROM contract
WHERE (l_start_date between start_date and end_date
OR
l_end_date between start_date and end_date)
AND l_name = name;
IF l_overlap_check = 0 THEN
INSERT INTO contract (name, start_date, end_date)
VALUES (l_name, l_start, l_end);
END IF;
END;
I don't have an Oracle environment handy but that should be what you need.

Related

Creating a Database Trigger that checks if more than one record was added on a date?

CREATE OR REPLACE TRIGGER POSITION_NUMBER
BEFORE UPDATE OR INSERT OR DELETE ON APPLIES
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
NUMBER_OF_POSITIONS NUMBER;
BEGIN
SELECT count(pnumber) INTO NUMBER_OF_POSITIONS
FROM APPLIES WHERE anumber = :NEW.anumber;
IF( NUMBER_OF_POSITIONS > 2 AND count(APPDATE) > 2 )
THEN
RAISE_APPLICATION_ERROR(-20000,'an Employee cannot apply for
more than two positions');
END IF;
END;
/
Im attemtping to create a trigger that goes off if an Applicant applys for more than two Positions on the Same Day, but im not sure how i would implement the Date side of it. Below is the set of relational Schemeas
You can use the TRUNC function to remove the time portion and then see if the application date matches today's date, regardless of time.
Also, there is no need for the autonomous transaction pragma. You are not executing any DML.
CREATE OR REPLACE TRIGGER position_number
BEFORE UPDATE OR INSERT OR DELETE
ON applies
DECLARE
number_of_positions NUMBER;
BEGIN
SELECT COUNT (pnumber)
INTO number_of_positions
FROM applies
WHERE anumber = :new.anumber AND TRUNC (appdate) = TRUNC (SYSDATE);
IF number_of_positions > 2
THEN
raise_application_error (
-20000,
'An Employee cannot apply for more than two positions on the same day');
END IF;
END;
/

PLSQL Converting if then to CASE stmt

Converting if then to CASE stmt. Please let me know what mistake I m making here
DECLARE
salary NUMBER;
bonus NUMBER;
hdate DATE;
empno NUMBER;
BEGIN
SELECT hiredate INTO hdate FROM emp where empno=7788 ;
CASE hdate
WHEN hdate > TO_DATE('01-JAN-82') THEN bonus := 500 DBMS_OUTPUT.PUT_LINE(bonus);
WHEN hdate > TO_DATE('23-JAN-16') THEN bonus := 1000 DBMS_OUTPUT.PUT_LINE(bonus);
ELSE bonus := 1500 DBMS_OUTPUT.PUT_LINE(bonus);
END CASE;
END;
/
Use the following syntax for CASE:
DECLARE
salary NUMBER;
bonus NUMBER;
hdate DATE;
empno NUMBER;
BEGIN
SELECT sysdate INTO hdate FROM dual ;
CASE
WHEN hdate > TO_DATE('01-JAN-82') THEN bonus := 500;
WHEN hdate > TO_DATE('23-JAN-16') THEN bonus := 1000 ;
ELSE bonus := 1500 ;
END CASE;
DBMS_OUTPUT.PUT_LINE(bonus);
END;
Notice that the WHEN clauses can use different conditions rather than all testing the same variable or using the same operator.
Perhaps another proposition; instead of CASE statement in WHERE clause, it is rather in the SELECT list to deter the usage of hdate, thus a single SQL to achieve the desired output.
DECLARE
salary NUMBER;
p_bonus NUMBER;
hdate DATE;
empno NUMBER;
BEGIN
SELECT CASE
WHEN hiredate > TO_DATE ('01-JAN-82') THEN 500
WHEN hiredate > TO_DATE ('23-JAN-16') THEN 1000
ELSE 1500
END
INTO p_bonus
FROM emp
WHERE empno = 7788;
DBMS_OUTPUT.put_line (p_bonus);
END;
/
In addition to the other answers, there are a couple of other things wrong with your case statement.
Dates
When you use to_date to explicitly convert a string into a date, you should also use a format model to describe the format of the string. By not doing so, you rely on the default NLS_DATE_FORMAT parameter, which could well be different on different machines.
Also, years have 4 digits - use all of them, rather than make Oracle guess. Does the 2 digit year 16 mean 2016 or 1916?
Far better to be explicit, in both cases!
Therefore, your date conditions should actually be to_date('01-JAN-1982', 'dd-MON-yyyy', 'nls_date_language=english') and to_date('23-JAN-2016', 'dd-MON-yyyy').
Note the presence of the optional third parameter - I used that because you specified the month in words, and again, your NLS_DATE_LANGUAGE parameter might not be the same on someone else's machine. Adding the third parameter means the string will be converted to a date regardless of your NLS settings.
You can avoid the use of the 3rd parameter in to_date by using numbers for the day, month and year, e.g. to_date('23/01/2016', 'dd/mm/yyyy', 'nls_date_language=english').
CASE and logic short circuiting
CASE uses logic short circuiting, meaning that when it evaluates a condition to be true, it doesn't process any further conditions.
It seems like you intend a hiredate of 23rd Feb 2017 to get a bonus of 1000, but since it meets the first condition (it's later than 1st Jan 1982), it gets a bonus of 500.
Therefore, you need to change the order of the conditions, so that the most restrictive is at the top. In your case, your procedure becomes:
DECLARE
salary NUMBER;
bonus NUMBER;
hdate DATE;
empno NUMBER;
BEGIN
SELECT hiredate
INTO hdate
FROM emp
WHERE empno=7788;
CASE hdate
WHEN hdate > TO_DATE('23-JAN-16') THEN bonus := 1000;
WHEN hdate > TO_DATE('01-JAN-82') THEN bonus := 500;
ELSE bonus := 1500 DBMS_OUTPUT.PUT_LINE(bonus);
END CASE;
DBMS_OUTPUT.PUT_LINE(bonus);
END;
/

Using a variable in PL/SQL

I am using PL/SQL in Toad for Oracle.
I would like to use define a variable in my code and then using this variable multiple times in the query.
Please note that I'm not asking for the pop-up window in which input the value of the variable; I need something like this:
DEFINE min_salary = 100
SELECT Surname FROM employees
WHERE salary < min_salary
I.e. min_salary in the WHERE statement assumes the value defined above.
Surfing the net, someone suggests to add an & before the variable in the where statement, i.e.
DEFINE min_salary = 100
SELECT Surname FROM employees
WHERE salary < &min_salary
But this is not useful in my case, since the & calls the pop-up window.
Instead, I would insert the value directly in the code.
Anyone could help?
A Select-Statement is not PL/SQL it's SQL. You need to create PL/SQL-Code:
DECLARE
min_salary employees.salary%TYPE := 100;
BEGIN
FOR i IN (SELECT Surname
FROM employees
WHERE salary < min_salary)
LOOP
DBMS_OUTPUT.put_line ('Surname: ' || i.Surname);
END LOOP;
END;
I don't know what you want to do, but you have to choose where to get the output. A PL/SQL-Script doesn't output the data-grid. You only run it.
You also could build a function to validate. Example:
CREATE OR REPLACE FUNCTION IsMinSalary (salary NUMBER)
RETURN NUMBER
IS
defaultMinSalary employees.salary%TYPE := 100;
BEGIN
IF (defaultMinSalary < salary)
THEN
RETURN 0;
ELSE
RETURN 1;
END IF;
END IsMinSalary;
/
SELECT surname
FROM (SELECT 10 AS Salary, 'ten' AS Surname FROM DUAL
UNION ALL
SELECT 100 AS Salary, 'hundred' AS Surname FROM DUAL
UNION ALL
SELECT 200 AS Salary, 'two-hundred' AS Surname FROM DUAL) t -- fake-table
WHERE IsMinSalary (t.salary) = 1

Deal with column list of multiple queries executed at runtime.

I have a list of queries stored in an Oracle DB table. My requirement is to fetch each of those queries one by one and fire them in a procedure and log their start end and elapsed times in another table
My problem is how should I handle the column list as that's going to be different for each of those queries and the number of columns and their datatypes cannot be anticipated at runtime.
Please suggest a way out.
For now, I have written down the code below. Here I have enclosed each query fetched with a count() to avoid the problem. However, the actual time taken for the count() query will be different from the time taken for the original query to execute.
Thanks a lot!
DECLARE
before_time TIMESTAMP;
after_time TIMESTAMP;
elapsed_time_in_ms NUMBER;
stmnt CLOB; --varchar(32000);
counts NUMBER;
sql_no NUMBER;
err_mess VARCHAR2(100);
CURSOR get_queries
IS
SELECT * FROM SLOW_RUNNING_SQL WHERE curr_ind = 1;
FUNCTION get_elapsed_time(
start_time_in TIMESTAMP ,
end_time_in TIMESTAMP )
RETURN NUMBER
AS
l_days NUMBER;
hours NUMBER;
minutes NUMBER;
seconds NUMBER;
milliseconds NUMBER;
BEGIN
<calculates elapsed time in milliseconds and returns that>
RETURN milliseconds ;
END;
BEGIN
dbms_output.put_line(CURRENT_TIMESTAMP);
before_time := SYSTIMESTAMP;
FOR i IN get_queries
LOOP
stmnt := i.SQL_DESC;
sql_no := i.sql_no;
stmnt := 'SELECT count(*) FROM ('||stmnt||') a';
dbms_output.put_line(stmnt);
EXECUTE IMMEDIATE stmnt INTO counts;
after_time := SYSTIMESTAMP;
elapsed_time_in_ms:= get_elapsed_time(before_time,after_time);
dbms_output.put_line(elapsed_time_in_ms);
INSERT
INTO query_performance_log VALUES
(
i.sql_no,
stmnt,
counts,
before_time,
after_time,
elapsed_time_in_ms/1000,
'No exception',
elapsed_time_in_ms );
dbms_output.put_line(stmnt);
dbms_output.put_line(counts);
dbms_output.put_line(after_time);
dbms_output.put_line(TO_CHAR(after_time - before_time));
COMMIT;
END LOOP;
ROLLBACK;
EXCEPTION
WHEN OTHERS THEN
err_mess:= SQLERRM;
INSERT
INTO query_performance_log VALUES
(
sql_no,
stmnt,
0,
NULL,
NULL,
0,
err_mess,
0
);
dbms_output.put_line(SQLERRM);
ROLLBACK;
END;
A solution that might suit you is to select a constant for every line returned by your query and make a bulk collect INTO a collection of varchar2 variables.
Here is what you're looking for:
-- declare a list of varchar2:
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
-- then use this type in your proc.:
[..]
declare
v_res t_my_list;
[..]
-- then run the query
execute immediate 'SELECT ''x'' FROM ('||stmnt||') '
bulk collect into v_res;
If the columns selected by your queries are "simple", the above should work fair enough to evaluate performances. But if you start calling other functions and procedures for the data you retrieve in the select, then its more complicate.
In this other case, then you should try to work something out to build a concatenation of the columns returned (and enlarge the VARCHAR2(100) in the declaration of t_my_list). This implies you start work on stmnt and extract the columns, a part being the replacement of , by ''||'' or so.

Table doesnt have an ineteger as a primary key how to perform operations on it?

This question was asked in one practical exams at my university.
Q: A table has following columnheads/columns:
medicine_name,medicine_manufacture_date,medicine_expiry_date.
As the columnhead names describe they have the respective data about a medicine in them.
Now they asked to write a pl/sql block to make list of all medicines' names which have expired(i.e.current system date is greater than the expirydate values of the medicine).
I strongly think that the column of sequential consecutive integers(like 1,2,3,...n) must be added to the table, which will act as a primary key to each medicine name.Medicine names are unique but to perform iterative operations they should posses an integer primary key.
Is it still possible to get the result without assigning an ineteger primary key?
I altered the table and assigned primary key to each record and wrote following block... It worked
but i want to do it without assigning a primary key. All possible smart ways will be adored!Plz suggest correction of any type in my code... My code is as follows:
declare
a date;
b date;
diff number(10);
medicine varchar(25);
begin
a:=sysdate;
for i in 1..5
loop
select medicine_expiry_date into b from med_details where med_id=i ;
diff:=trunc(a-b);
if
diff>0 then
select medicine_name into medicine from med_details where med_id=i;
dbms_output.put_line(medicine);
end if;
end loop;
end;
/
Try
BEGIN
DBMS_OUTPUT.PUT_LINE('The following medicines have expired:');
FOR aRow IN (SELECT MEDICINE_NAME
FROM MEDICINE
WHERE EXPIRY_DATE < SYSDATE)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.MEDICINE_NAME);
END LOOP;
END;
You could put an artificial primary key on this table if there was some need to have some other table refer to this one and no other practical candidate key was available, but it's not needed to answer the question as asked above.
Share and enjoy.
It looks straightforward... I suspect that there are some extra details.
/*
Q: A table has following columnheads/columns:
medicine_name,
medicine_manufacture_date,
medicine_expiry_date.
Now they asked to write a pl/sql block to make list of all medicines' names which have expired
(i.e.current system date is greater than the expirydate values of the medicine).
*/
-- SQL:
SELECT medicine_name
FROM a_table
WHERE medicine_expiry_date < SYSDATE;
-- PL/SQL v.1:
DECLARE
TYPE medicine_ntt IS TABLE OF a_table%ROWTYPE;
l_medicine medicine_ntt;
BEGIN
SELECT medicine_name
, medicine_expiry_date
BULK COLLECT INTO l_medicine
FROM a_table;
FOR indx IN 1..l_medicine.COUNT LOOP
IF l_medicine(indx).medicine_expiry_date < SYSDATE THEN
DBMS_OUTPUT.PUT_LINE(l_medicine(indx).medicine_name);
END IF;
END LOOP;
END;
-- PL/SQL v.2:
DECLARE
CURSOR medicine_cur IS
SELECT medicine_name
, medicine_expiry_date
FROM a_table;
TYPE medicine_ntt IS TABLE OF medicine_cur%ROWTYPE;
l_medicine medicine_ntt;
l_medicine_expired medicine_ntt := medicine_ntt();
BEGIN
OPEN medicine_cur;
FETCH medicine_cur BULK COLLECT INTO l_medicine;
CLOSE medicine_cur;
FOR indx IN 1..l_medicine.COUNT LOOP
IF l_medicine(indx).medicine_expiry_date < SYSDATE THEN
l_medicine_expired.EXTEND;
l_medicine_expired(l_medicine_expired.LAST) := l_medicine(indx);
END IF;
END LOOP;
FOR indx IN 1..l_medicine_expired.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(l_medicine_expired(indx).medicine_name);
END LOOP;
END;

Resources