Thanks, for idea
I have to write a procedure that computes number of project, and
average working hours of employees where employee id is passed as a
parameter to the procedure. If the average working hours is less than
10 then employee’s salary remain the same, otherwise check if number
of project is less than 4 then 5% of salary, else 10% of salary is
added to the salary.
something wrong with my output in plsql statement it is not displaying
new salary
here is my plsql procedure:
create or replace procedure view_data(p_id number) is
cursor c1
is
select count(distinct a.projectid) as PRJ_COUNT, e.empid, e.empname, e.salary as old_sal, round(avg(a.hours),0)as AVG_HOURS
from assignment a join employee e
on a.empid=e.empid
where e.empid=p_id
group by e.empid,e.empname, e.salary;
creader c1%rowtype;
cursor c2
is
select empid, salary as new_sal
from employee
where empid=p_id for update of salary;
rate_rec c2%rowtype;
v_new_salary number;
begin
--open c1;
--fetch c1 into creader;
for creader in c1 loop
for rate_rec in c2 loop
if creader.avg_hours < 10 then
update employee set salary=rate_rec.new_sal
where empid=rate_rec.empid;
elsif creader.prj_count<4 then
update employee set salary=rate_rec.new_sal+rate_rec.new_sal*0.05
where empid=rate_rec.empid;
else
update employee set salary=rate_rec.new_sal+rate_rec.new_sal*0.1
where empid=rate_rec.empid;
end if;
select salary into v_new_salary from employee
where empid=creader.empid;
dbms_output.put_line('Employee ID: '||creader.empid);
dbms_output.put_line('Employee Name: '||creader.empname);
dbms_output.put_line('Number of projects: '||creader.prj_count);
dbms_output.put_line('Average Working Hours: '||creader.avg_hours);
dbms_output.put_line('Old Salary: '||rate_rec.new_sal);
dbms_output.put_line('New Salary: '||v_new_salary);
end loop;
end loop;
end view_data;
/
here is the output:
Employee ID: 101
Employee Name: Marlen
Number of projects: 3
Average Working Hours: 87
Old Salary: 39206
New Salary: 41166
solution with using cursors:
create or replace procedure view_data(p_id number) is
cursor c1
is
select count(distinct a.projectid) as PRJ_COUNT, e.empid, e.empname, e.salary as old_sal, round(avg(a.hours),0)as AVG_HOURS
from assignment a join employee e
on a.empid=e.empid
where e.empid=p_id
group by e.empid,e.empname, e.salary;
creader c1%rowtype;
cursor c2
is
select empid, salary as new_sal
from employee
where empid=p_id for update of salary;
rate_rec c2%rowtype;
v_new_salary number;
v_bonus number;
begin
for creader in c1 loop
for rate_rec in c2 loop
if creader.avg_hours >= 10 then
if creader.prj_count<4 then
update employee set salary=salary*1.05
where empid=rate_rec.empid
return salary into v_new_salary;
else
update employee set salary=salary*1.1
where empid=rate_rec.empid
returning salary into v_new_salary;
end if;
end if;
v_bonus:=v_new_salary-rate_rec.new_sal;
dbms_output.put_line('Employee ID: '||creader.empid);
dbms_output.put_line('Employee Name: '||creader.empname);
dbms_output.put_line('Number of projects: '||creader.prj_count);
dbms_output.put_line('Average Working Hours: '||creader.avg_hours);
dbms_output.put_line('Old Salary: '||creader.old_sal);
dbms_output.put_line('Bonus: '||v_bonus);
dbms_output.put_line('New Salary: '||v_new_salary);
end loop;
end loop;
end view_data;
/
The variable rate_rec is in scope in the loop, not outside. You should put all your put_line inside the loop.
rate_rec.new_sal is the old salary. The new one will be updated in the table, not in the record with the previous salary.
I propose that you do your updates and then you select once again and check that is has been modified. You could also use a returning clause in the updates to get the new salary.
Here is a possible solution. I have not checked thoroughly the query. There should probably be a commit at the end of the procedure.
set serveroutput on
create or replace procedure update_data(p_id number) is
prj_count integer;
avg_hours integer;
old_salary number;
new_salary number;
begin
select count(a.projectid), round(avg(a.hours),0), e.salary
into prj_count, avg_hours, old_salary
from assignment a join employee e on a.empid=e.empid
where e.empid=p_id
group by e.salary;
new_salary := old_salary;
if avg_hours >= 10 then
if prj_count<4 then
update employee set salary=salary+salary*0.05
where empid=p_id
returning salary into new_salary;
else
update employee set salary=salary+salary*0.1
where empid=p_id
returning salary into new_salary;
end if;
end if;
dbms_output.put_line('Old salary: ' || old_salary);
dbms_output.put_line('New salary: ' || new_salary);
end update_data;
/
Related
I have the following code extremely slow code in a MS SQL 2016 Stored Procedure (I think it is stuck in a never ending loop):
#DBID Integer
AS
BEGIN
DECLARE TagCursor CURSOR FOR SELECT MemberID
FROM ADMIN_API_Master_Members_List
WHERE DBID= #DBID AND Len(Pending) > 0
DECLARE #ProgramCode VARCHAR(10)
DECLARE #Values VARCHAR(MAX)
DECLARE #tag nvarchar(10)
OPEN TagCursor
FETCH NEXT FROM TagCursor INTO #tag
WHILE (##FETCH_STATUS = 0)
BEGIN
SELECT #ProgramCode = Program_Code, #Values= Pending FROM ADMIN_API_Master_Members_List WHERE MemberID= #tag
DELETE FROM ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE
WHERE (MemberID =#tag)
INSERT INTO ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE ( ProgramCode, MemberID, DBID, PMID)
SELECT #ProgramCode, #tag, #DBID , Value FROM STRING_SPLIT(#Values, ',')
END
CLOSE TagCursor
DEALLOCATE TagCursor
END
This Procedure is only a maintenance process and does not get run very often but when it does it would be nice to only take a few seconds. The purpose is to normalize in a Table one record for each comma seperated value in the ADMIN_API_Master_Members_List Table and put it into the ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE with the Program_Code and MemberID and DBID.
There are only about 150 records in the Master Table and the comma separated strings may have 5 values. I am receptive to other solutions.
Thanks in advance
I haven't tested this, but like I mentioned in the comments, using a CURSOR is a bad idea; they are inherently slow as SQL Server excels at set based methods not iterative tasks (and a CURSOR is the latter).
I suspect that this achieves the answer you're after and avoids the CURSOR all together:
CREATE PROC YourProc #DBID integer
AS
BEGIN
DECLARE #Deleted table (ProgramCode varchar(10),
[Value] varchar(MAX),
Tag nvarchar(10));
DELETE HT
OUTPUT deleted.Program_Code,
deleted.Pending,
deleted.MemberID
INTO #Deleted (ProgramCode,
[Value],
Tag)
FROM ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE AS HT
JOIN ADMIN_API_Master_Members_List AS MML ON HT.MemberID = MML.MemberID
WHERE MML.[DBID] = #DBID
--AND LEN(Pending) > 0; --Changed this to below to be SARGable, as only a string with the value '' will have a length of 0.
AND Pending != '';
INSERT INTO ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE (ProgramCode,
MemberID,
DBID,
PMID)
SELECT D.ProgramCode,
D.Tag,
#DBID,
SS.Value
FROM #Deleted AS D
CROSS APPLY STRING_SPLIT(D.[Value], ',') AS SS;
END;
The reason for the infinite loop may be that you have no "Fetch next" inside your loop
Try the below:
#DBID Integer
AS
BEGIN
DECLARE TagCursor CURSOR FOR SELECT MemberID
FROM ADMIN_API_Master_Members_List
WHERE DBID= #DBID AND Len(Pending) > 0
DECLARE #ProgramCode VARCHAR(10)
DECLARE #Values VARCHAR(MAX)
DECLARE #tag nvarchar(10)
OPEN TagCursor
FETCH NEXT FROM TagCursor INTO #tag
WHILE (##FETCH_STATUS = 0)
BEGIN
SELECT #ProgramCode = Program_Code, #Values= Pending FROM ADMIN_API_Master_Members_List WHERE MemberID= #tag
DELETE FROM ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE
WHERE (MemberID =#tag)
INSERT INTO ADMIN_API_PMID_PROGRAM_CODE_HOLDING_TABLE ( ProgramCode, MemberID, DBID, PMID)
SELECT #ProgramCode, #tag, #DBID , Value FROM STRING_SPLIT(#Values, ',')
FETCH NEXT FROM TagCursor INTO #tag
END
CLOSE TagCursor
DEALLOCATE TagCursor
END
I am working on the following Oracle (PL/SQL) stored procedure:
Procedure myProc(idParam IN Number, RESULT OUT SYS_REFCURSOR)
IS
BEGIN
FOR myMetaData in (select status, idData from Table1 where id=idParam)
LOOP
IF myMetaData.status='test1'
SELECT column1, column2, column3 from Table2 where cond1=cond2;
ELSE
SELECT column1, column2, column3 from Table2 where column4=
(select column4 from.....);
END IF;
END LOOP;
END myProc;
Assuming above is my code now I need to return combined results from both IF clause select statement and Else clause. I tried with dbms_sql.return_result(); but it didn't help.
How can I combine both result sets and return the value?
There are many ways to achieve your requirement however i would prefer to use a table to achieve your requirement. See below:
--Create a table to hold your result of if clause
Create table Rslt (col1 number,col2 number, col3 number);
/
--Use sysrefcursor to get the final result out of the table
Procedure myProc(idParam IN Number, RESULT OUT SYS_REFCURSOR)
IS
BEGIN
FOR myMetaData in (select status, idData from Table1 where id=idParam)
LOOP
IF myMetaData.status='test1'
insert into rslt SELECT column1, column2, column3 from Table2 where cond1=cond2;
ELSE
insert into rslt SELECT column1, column2, column3 from Table2 where column4=
(select column4 from.....);
END IF;
END LOOP;
Open result for select * from rslt;
END myProc;
Another apporach could be Object Oriented using a Object having table columns. See below:
Create type rslt is object
(col1 number,
col2 number,
col3 number
);
Create type var_rslt is table of rslt ;
Procedure myProc(idParam IN Number, V_RESULT OUT SYS_REFCURSOR)
IS
v_rslt1 var_rslt:=var_rslt();
v_rslt2 var_rslt:=var_rslt();
v_rslt3 var_rslt:=var_rslt();
v_rslt4 var_rslt:=var_rslt();
BEGIN
FOR myMetaData in (select status, idData from Table1 where id=idParam)
LOOP
IF myMetaData.status='test1'
SELECT rslt(column1, column2, column3) bulk collect into v_rslt1 from Table2 where cond1=cond2;
v_rslt2:=v_rslt2 Multiset union all v_rslt1;
ELSE
SELECT rslt(column1, column2, column3) bulk collect into v_rslt13 from Table2 where column4= (select column4 from.....);
v_rslt4:=v_rslt4 multiset union all v_rslt13;
END IF;
END LOOP;
v_rslt2 := v_rslt2 multiset union all v_rslt4;
OPEN V_RESULT FOR SELECT * FROM table( v_rslt2 );
END myProc;
Demo:
Table Preparation:
Create table Table1 (id number, status varchar2(10));
/
Insert into table1 values(1,'test1');
Insert into table1 values(2,'test2');
Create table Table2 (id number,column1 number, column2 number, column3 number);
/
insert into table2 values(1,10,20,30);
insert into table2 values(1,70,60,50);
insert into table2 values(1,20,40,30);
insert into table2 values(2,80,40,20);
insert into table2 values(2,60,20,10);
Create type rslt is object
(col1 number,
col2 number,
col3 number
);
Create type var_rslt is table of rslt ;
Procedure:
CREATE OR REPLACE Procedure myProc(idParam IN Number, V_RESULT OUT sys_refcursor)
IS
v_rslt1 var_rslt:=var_rslt();
v_rslt2 var_rslt:=var_rslt();
v_rslt3 var_rslt:=var_rslt();
v_rslt4 var_rslt:=var_rslt();
BEGIN
FOR myMetaData in (select status, id from Table1)
LOOP
IF myMetaData.status='test1' then
SELECT rslt(column1, column2, column3) bulk collect into v_rslt1 from Table2 where id=myMetaData.id;
v_rslt2:=v_rslt2 Multiset union all v_rslt1;
ELSE
SELECT rslt(column1, column2, column3) bulk collect into v_rslt3 from Table2 where id=myMetaData.id;
v_rslt4:=v_rslt4 multiset union all v_rslt3;
END IF;
END LOOP;
v_rslt4 := v_rslt4 multiset union all v_rslt2;
open V_RESULT for Select * from table(v_rslt4);
END myProc;
Execution:
DECLARE
var sys_refcursor;
var1 NUMBER;
var2 NUMBER;
var3 NUMBER;
BEGIN
myProc(1, var);
LOOP
FETCH var INTO var1,var2,var3;
EXIT WHEN var%notfound;
dbms_output.put_line(var1);
END LOOP;
END;
OUTPUT:
SQL> /
anonymous block completed
80
60
10
70
20
Note: This solution will work on Oracle 11g and onward versions. Incase you are working on lower version of Oracle, then you need to modify the Object definition as below:
Create type rslt is object
(col1 number,
col2 number,
col3 number,
map member function mem return number);
This is due to a bug in Oracle 10g while using MULTISET operator.
Read more about the bug at http://raajeshwaran.blogspot.com/2010/07/pls-00801-internal-error-assert-at-file.html
I want to create a stored procedure that only does inserts in the month of March. The stored procedure should accept values for the table but use the system date to determine if the records should be inserted.
This is what I was trying but procedure created with errors.
CREATE OR REPLACE PROCEDURE sp_time_1203383 (
p_sales_id IN sales_1203383.SALES_ID%TYPE,
p_product IN sales_1203383.PRODUCT%TYPE,
p_unitcost IN sales_1203383.UNITCOST%TYPE,
p_quantity IN sales_1203383.QUANTITY%TYPE
)
IS
BEGIN
IF( MONTH( GETDATE() ) = 3 )
BEGIN
INSERT INTO sales_1203383 ("SALES_ID", "PRODUCT", "UNITCOST", "QUANTITY")
VALUES (p_sales_id, p_product,p_unitcost, p_quantity);
END
ELSE
BEGIN
SELECT 'Can Only insert during the month of March'
END
COMMIT;
END;
I think Praveen gave you the right advice.
I personally would not use the
SELECT count(1) into v_cnt FROM dual WHERE month(sysdate) = 3;
but you can check in a if ... then ... else ... end; statement with the following code:
if to_char(sysdate,'mm') = '03' then ... end;
There are a few errors in your stored procedure:
You cannot have select with if statement
Use then with IF statement and at end End if
; has to be used with End statement
You cannot return a select set from a procedure/function/block (ref cursor should be used)
If you want raise error in the else part use raise_application_error.
If your select statement is like select 'something' use dummy table dual, select 'something' from dual
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CREATE OR REPLACE PROCEDURE sp_time_1203383 (
p_sales_id IN sales_1203383.SALES_ID%TYPE,
p_product IN sales_1203383.PRODUCT%TYPE,
p_unitcost IN sales_1203383.UNITCOST%TYPE,
p_quantity IN sales_1203383.QUANTITY%TYPE
)
IS
v_cnt NUMBER;
BEGIN
SELECT count(1) into v_cnt FROM dual WHERE month(sysdate) = 3;
IF v_cnt > 0 THEN
INSERT INTO sales_1203383 ("SALES_ID", "PRODUCT", "UNITCOST", "QUANTITY")
VALUES (p_sales_id, p_product, p_unitcost, p_quantity);
ELSE
raise_application_error(-20101, 'Can Only insert during the month of March');
END IF;
END;
What is the best way to get an autoincrement "counter" thath depends of other field?.
Imagine this table
CUSTOMER - COUNTER
1 - 1
1 - 2
1 - 3
2 - 1
2 - 2
I need Counter increments 1 every record I add for every customer.
Regards.
Create additional table to hold counters for every customer:
CREATE TABLE customer_counter
(
customer_id INTEGER NOT NULL,
counter INTEGER NOT NULL,
PRIMARY KEY (customer_id)
)
Use following procedure to obtain next counter number for given customer:
CREATE PROCEDURE get_customer_counter (customer_id INTEGER)
RETURNS (counter INTEGER)
AS
BEGIN
SELECT SUM(counter) FROM customer_counter
WHERE customer_id = :customer_id
INTO :counter;
counter = COALESCE(:counter, 0);
EXECUTE STATEMENT
'INSERT INTO customer_counter (customer_id, counter) ' ||
'VALUES (' || :customer_id || ', 1)'
WITH AUTONOMOUS TRANSACTION;
END
See how we use sum and insert delta values instead of updating single record. Thus we prevent deadlocks.
On regular basis we need to clear counter table by merging delta records into one with a total value:
CREATE TRIGGER on_disconnect_database
ACTIVE
ON DISCONNECT
AS
DECLARE VARIABLE sm INTEGER;
DECLARE VARIABLE cnt INTEGER;
DECLARE VARIABLE customer_id INTEGER;
BEGIN
FOR
SELECT customer_id, SUM(counter), COUNT(counter)
FROM customer_counter
GROUP BY customer_id
INTO :customer_id, :sm, :cnt
DO BEGIN
IF (:cnt > 1) THEN
BEGIN
DELETE FROM customer_counter WHERE customer_id = :customer_id;
INSERT INTO customer_counter (customer_id, counter)
VALUES (:customer_id, :sm);
WHEN ANY DO
BEGIN
END
END
END
END
It's hard to really tell what you exactly want of what you have given, but I assume you want to loop through each X field and it's Y sub-field, use it for a counter, or whatever else.
<?php
$x = array("customer1","customer2","customer3");
$y = array("name","address","phone");
$counter;
foreach($x as $eachX):
$counter = 1;
foreach($y as $eachY):
//do stuff here
$counter++;
endforeach;
endforeach;
?>
I think this stored procedure will be ok!
create procedure New_Procedure
returns (
o_customer integer,
o_counter integer)
as
declare variable v_oldcostomer integer;
begin
for select customer
from customers
order by 1
into o_customer
do begin
if (v_oldcostomer is null or (v_oldcostomer is not null and v_oldcostomer <> o_customer) ) then
o_counter = 0;
o_counter = o_counter + 1;
v_oldcostomer = o_customer;
suspend;
end
end
I have two tables.
1- student table & 2- Score table
I want to insert value at student table & insert multi value at Score table with SP to SQL Server 2008.
for EX:
ALTER proc [dbo].[InsertIntoScore]
(
#DateReg datetime,
#stdLastName nvarchar(50),
#stdFirstName nvarchar(50),
#Description nvarchar(500),
multi value as score table...
)
AS
DECLARE #Id AS INT
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO Student(DateReg,stdLastName,stdFirstName,[Description])
VALUES (#DateReg,#stdLastName,#stdFirstName,#Description)
set #Id = SCOPE_IDENTITY()
insert multi value at Score table...
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK
END CATCH
please help me...
You should use Table-Valued Parameters
Create a Sql Type for the table you will pass in
CREATE TYPE dbo.ScoreType AS TABLE ( ScoreID int, StudentID int, etc.... )
pass your datatable from C# code into the stored procedure using the above defined type
ALTER proc [dbo].[InsertIntoScore]
( #DateReg datetime, #stdLastName nvarchar(50), #stdFirstName nvarchar(50),
#Description nvarchar(500), #tvpScore ScoreType)
AS
.....
INSERT INTO dbo.Score (ScoreID, StudentID, ....)
SELECT dt.ScoreID, #id, .... FROM #tvpScore AS dt;
in c# pass the datatable in this way
SqlCommand insertCommand = new SqlCommand("InsertIntoScore", sqlConnection);
SqlParameter p1 = insertCommand.Parameters.AddWithValue("#tvpScore", dtScore);
p1.SqlDbType = SqlDbType.Structured;
p1.TypeName = "dbo.ScoreType";
.....
insertCommand.ExecuteNonQuery();