Using a variable in PL/SQL - plsql

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

Related

Updating salary of employees when their salary is less than a threshold kept

I have this problem, I want to update salary of employees whose salary is less than 0.5 of managers salary using plsql procedure, it compiles but when ever I try to exec the code it doesn't work. any idea what the problem might be? maybe from the code?
CREATE OR REPLACE PROCEDURE INC_SALARY(PRC NUMBER)
IS
BEGIN
LOOP
UPDATE EMP
SET SALARY = SALARY * PRC
WHERE EMP_ID IN(
SELECT E.EMP_ID
FROM EMP E
JOIN EMP M
ON E.MANAGER_ID = M.EMP_ID
WHERE E.SALARY < M.SALARY * 0.5 );
END LOOP;
END INC_SALARY;
EXEC INC_SALARY(1.20);
According to your answer in the comment, you completely misunderstood what an UPDATE statement does - check here UPDATE.
One UPDATE statement is enough to meet your requirements.
LOOP is never needed in such scenario, unless you wanted to fetch every single record (using SELECT statement) and execute an UPDATE one by one (also known as slow by slow).
To see how many employees gets updated with your one UPDATE statement, you can execute your query used in UPDATE's WHERE statement:
SELECT
COUNT(E.EMP_ID)
FROM EMP E
JOIN EMP M
ON E.MANAGER_ID = M.EMP_ID
WHERE E.SALARY < M.SALARY * 0.5
I added a few comments to your code to explain to you, what your code actually does:
CREATE OR REPLACE PROCEDURE INC_SALARY(PRC NUMBER)
IS
BEGIN
-- Enter the loop and start executing the code into the loop
-- over and over again
LOOP
-- Update EVERY employee whose salary is less than
-- a half of the salary of his manager
-- setting his salary to his salary multiplied by PRC parameter
UPDATE EMP
SET SALARY = SALARY * PRC
WHERE EMP_ID IN(
SELECT E.EMP_ID
FROM EMP E
JOIN EMP M
ON E.MANAGER_ID = M.EMP_ID
WHERE E.SALARY < M.SALARY * 0.5
);
-- Update finished - loop starts another iteration
-- and calls Update for EVERY employee over and over again
-- Since there's no exit from the loop
-- (loop never ends, iterates infinitely)
-- the execution never ends
END LOOP;
END INC_SALARY;
In my opinion whenever possible try to use SQL rather using PLSQL. In this case I can see this can be done via single UPDATE query. If any specific reason for Procedure then go for it otherwise try to use simple UPDATE
UPDATE EMP
SET SALARY = SALARY * PRC
WHERE EMP_ID IN(
SELECT E.EMP_ID
FROM EMP E
JOIN EMP M
ON E.MANAGER_ID = M.EMP_ID
WHERE E.SALARY < M.SALARY * 0.5
);

PL/SQL - Inserting data using Exception

I have the following code which is not executing correctly. I have data stored in date_tmp (varchar) that includes dates and nondates. I want to move the dates in that column to date_run (date) and data that is not a date, will be moved to a comments (varchar) column. When I run the following code, the entire set of data gets moved to comments. It runs fine when I edit out the insert statement and just run the dbms_outputline line. What might I be doing incorrectly?
DECLARE
CURSOR getrow IS
SELECT a.id, a.date_tmp
FROM mycolumn a
WHERE a.id < 1300;
v_date date;
BEGIN
FOR i in getrow LOOP
BEGIN
v_date := to_date(i.date_tmp, 'mm/dd/yy');
INSERT INTO mycolumn a(a.date_run)
VALUES(i.date_tmp);
EXCEPTION
WHEN OTHERS THEN
--dbms_output.put_line(i.date_tmp);
update mycolumn a
SET a.comments = i.date_tmp
where a.id = i.id;
END;
END LOOP;
END;
You try to insert varchar i.date_tmp into a date field. Instead insert v_date.
...
INSERT INTO mycolumn a (a.date_run)
VALUES(v_date);
...
But actually your requirement is a move. That calls for an update actually. So I think what you really want to do is:
...
update mycolumn a
SET a.date_run = v_date
where a.id = i.id
...
And actually you could have a function that checks if you have a valid date or not and then you might be able to handle the whole task using a simple update statement.
create or replace function is_a_date(i_date varchar2, i_pattern varchar2)
return date
is
begin
return to_date(i_date, i_pattern);
exception
when others return null;
end is_a_date;
With that function you could write two update statements
update mycolumn
set date_run = to_date(date_tmp,'dd/mm/yy')
where is_a_date(date_tmp, 'dd/mm/yy') is not null;
update mycolumn
set comment = date_tmp
where is_a_date(date_tmp, 'dd/mm/yy') is null;
I designed the function in a way that you could use it in various ways as it returns you a date or null but no exception if the varchar does not conform to the date pattern.
You have an insert where it looks like you need an update, like you have in the exception handler. So just change it to:
v_date := to_date(i.date_tmp, 'mm/dd/yy');
update mycolumn
set date_run = v_date
where id = i.id;
or you could shorten it to:
update mycolumn
set date_run = to_date(i.date_tmp, 'mm/dd/yy')
where id = i.id;
#hol solution is the best approach for me.
Avoid always you can loops and procedures if you can do it with simple SQL statments, your code will be more faster.
Also, if you have always have a data fixed format , you can ride of the PL/SQL function is_a_date function and do it everything with SQL... but the code gets a little uglier with something like this:
update mycolumn
set date_run = to_date(date_tmp,'dd/mm/yy')
where substr(date_tmp,1,2) between '1' and '31'
and substr(date_tmp,4,2) between '1' and '12'
and substr(date_tmp,7,2) between '00' and '99';
If you need more speed in your query or you have a huge amount of data in date_tmp, as function is_a_date is deterministic (always returns the same value given the same values for X, Y,), you can create an index for it:
create index mycol_idx on mycolumn(is_a_date(date_tmp));
And when you use the function, Oracle will use your index, like in those selects:
SELECT a.id, a.date_tmp
FROM mycolumn a
WHERE a.id < 1300
and is_a_date(a.date_tmp) is not null;
SELECT a.id, a.date_tmp
FROM mycolumn a
WHERE a.id < 1300
and (is_a_date(a.date_tmp) is not null and is_a_date(a.date_tmp)>sysdate-5);

How to stop updating null values in oracle

I have a sql procedure which perfectly works. please find it below.
declare
cid number;
cadd number;
ctras number;
pr varchar(2);
vad number;
cursor c1 IS
select ac_tras, cust_id, cust_addr from customer_master;
cursor c2 IS
select pr_adr from customer_address where cust_id = cid and cust_addr = cadd;
BEGIN
open c1;
LOOP
fetch c1 into ctras, cid, cadd;
EXIT WHEN C1%NOTFOUND;
OPEN c2;
LOOP
fetch c2 into pr;
if pr='Y'
THEN EXIT ;
ELSE
UPDATE customer_master
set cust_addr = (select cust_addr from customer_address where pr_adr = 'Y' and cust_id = cid) where ac_tras = ctras;
END IF;
EXIT WHEN C2%NOTFOUND;
END LOOP;
Close C2;
END LOOP;
CLOSE C1;
END;
Everything works fine. The problem is, The update statement updates null if the sub query returns null. How to avoid this.
If the subquery doesn't find a matching row then the master table will be updated with null, because ther eis no filter to stop that. A common way to avoid that is to check that a matching row does exist:
UPDATE customer_master
set cust_addr = (
select cust_addr from customer_address
where pr_adr = 'Y' and cust_id = cid)
where ac_tras = ctras
and exists (
select cust_addr from customer_address
where pr_adr = 'Y' and cust_id = cid)
;
It doesn't really matter which column name you use in the exists clause; some people prefer to use select * or select null but it seems to be a matter of taste really (unless you specify a column you aren't going to be using later and which can't be retrieved from an index you're using anyway, which could force an otherwise unnecessary table row lookup).
You could also do a merge. And has been pointed out several times now, you don't need cursors or any PL/SQL to do this.

pass curosr variable to subquery in cirsor iterating loop

1 what is my purpose:
I try to get two person from each department with highest salary.
2 how I try to achieve it:
DECLARE
TYPE empl_table IS TABLE OF employees.employee_id%type INDEX BY binary_integer;
empl empl_table;
CURSOR departmennts_id IS
SELECT department_id FROM departments;
BEGIN
FOR depart_row IN departmennts_id
loop
SELECT employee_id BULK COLLECT into empl
FROM
(
SELECT employee_id
FROM employees
where DEPARTMENT_ID= depart_row.department_id
ORDER BY salary DESC
)
WHERE ROWNUM<3;
END loop;
END;
3 where is the problem:
where DEPARTMENT_ID= depart_row.department_id
When I change depart_row.department_id for fixed id number(ex. 80)
query works. If I use depart_row.department_id empl.count is 0.
Where I am making mistake?
For each iteration of your outer cursor you're putting rows into EMPL_TABLE. Each time that the code loops back for another department_id and then re-executes the inner SELECT it replaces the contents of the collection. Thus, if the LAST department seen by the outer cursor happens to have no employees associated with it, you end up with an empty collection.
Your best bet is to eliminate the separate cursor on DEPARTMENTS and just use a single cursor that does everything you want, as in:
SELECT *
FROM (SELECT DEPARTMENT_ID,
SALARY,
ROW_NUMBER() OVER
(PARTITION BY DEPARTMENT_ID
ORDER BY SALARY DESC) AS EMP_RANK
FROM EMPLOYEES
ORDER BY DEPARTMENT_ID, EMP_RANK)
WHERE EMP_RANK < 3
SQLFiddle here.
Share and enjoy.

How i can pass column names from variables in plsql update statement

DECLARE
v_name A.TRANSACTION_TYPE%TYPE :='SALARY';
v_salary A.SALARY%TYPE := 1000;
BEGIN
update A set v_name= v_salary where EMPID = 517;
-- PL/SQL: ORA-00904: "v_name": invalid identifier
--update A set SALARY = 1000 where EMPID = 517;
END;
/
My idea is to update table columns , but these column names are stored in variable. Is there any way to pass column names from variable ? Is there any options apart from Execute Immediate
Not sure if this will work in your situation, but I've written solutions where I wrote a script in SQLPlus and it "wrote" (using dbms_output.put_line or even just prompt) another script that did queries, and the columns/tables in those queries was determined by the logic in the SQLPlus script. Then I would execute as a script the output from my first script, and it would execute dynamically generated queries without ever needing execute immediate.
The following idea may work for multiple columns that are typed the same... As written, it will update all columns every time for a given record, but only the column specified by v_name will be changed to the value set in v_value; the other columns are simply updated to their existing value. The idea can be played with using DECODE, NVL or other similar conditional operators.
declare
v_name varchar2(20):= 'SAL';
v_value emptest.sal%TYPE := 5000;
begin
update emptest
set sal = ( select case when v_name = 'SAL' then v_value else sal end from dual),
comm = ( select case when v_name = 'COMM' then v_value else comm end from dual)
where empno = 7369;
commit;
end;

Resources