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.
Related
I have create a table person(id, name ,samenamecount).The samenamecount attribute can be null but for each row can store the row count for same names.I am achieving this by calling a stored procedure inside a after insert trigger.Below is my code.
create or replace procedure automatic(s in person.name%type)
AS
BEGIN
update person set samenamecount=(select count(*) from person where name=s) where name=s;
END;
create or replace trigger inserttrigger
after insert
on person
for each row
declare
begin
automatic(:new.name);
end;
On inserting a row it is giving error like
table ABCD.PERSON is mutating, trigger/function may not see it.
Can somebody help me to figure out this?
If you have the table:
CREATE TABLE person (
id NUMBER
GENERATED ALWAYS AS IDENTITY
CONSTRAINT person__id__pk PRIMARY KEY,
name VARCHAR2(20)
NOT NULL
);
Then rather than creating a trigger, instead, you could use a view:
CREATE VIEW person_view (
id,
name,
samenamecount
) AS
SELECT id,
name,
COUNT(*) OVER (PARTITION BY name)
FROM person;
You can use the trigger:
CREATE TRIGGER inserttrigger
AFTER INSERT ON person
BEGIN
MERGE INTO person dst
USING (
SELECT ROWID AS rid,
COUNT(*) OVER (PARTITION BY name) AS cnt
FROM person
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE SET samenamecount = src.cnt;
END;
/
fiddle
If you want to make it more efficient then you could use a compound trigger and collate the names that are being inserted and only update the matching rows.
I am a complete novice to PL/SQL and this is an attempt to start writing stored procedures . The case is i want to simply find out inside my stored procedure what is the name of the employee who is getting the highest salary for a particular department (which is passed as a parameter to the stored procedure).
Below is a screen shot of my tables:
The code of my stored procedure is as follows :
create or replace procedure High_salary(Dept_Name IN varchar2)
/*RETURN varchar2 */
AS
EMP_NAME_var varchar2(100) := '';
Begin
dbms_output.put_line('****'||Dept_Name);
select EMP_NAME INTO EMP_NAME_var
from(
select EMP_NAME,rank() over(order by salary desc) rn from employee
where DEPT_CD=(select DEPT_CD from DEPARTMENT where DEPT_NAME=Dept_Name)) a where rn=1;
/*RETURN EMP_NAME_var;*/
END;
when i run this i get this error:
Connecting to the database LOCAL_DEV_DB.
ORA-01427: single-row subquery returns more than one row
ORA-06512: at "LOCAL_DEV_DB.HIGH_SALARY", line 7
ORA-06512: at line 6
****'Technology'
Process exited.
Disconnecting from the database LOCAL_DEV_DB.
However when i run the subquery separate it gets only one row as expected :
select EMP_NAME
from(
select EMP_NAME,rank() over(order by salary desc) rn from employee
where DEPT_CD=(select DEPT_CD from DEPARTMENT where DEPT_NAME='Technology')) a where rn=1;
Can someone please point out what i am missing here.
The problem most likely comes from the fact that your variable name is the same as the field name. In the procedure, it is comparing Dept_Name with itself, and unsurprisingly, all rows match.
In the procedure, try naming the variable DeptNameVar or similar, update the reference in the query, and see if that helps.
I would express your logic as a join between the two tables. Then, use ROW_NUMBER to identify the record for a given department corresponding to the employee with the highest salary.
create or replace procedure High_salary (Dept_Name IN varchar2)
AS
EMP_NAME_var varchar2(100) := '';
Begin dbms_output.put_line('****'||Dept_Name);
select EMP_NAME INTO EMP_NAME_var
from
(
select e.EMP_NAME, ROW_NUMBER() OVER (ORDER BY e.SALARY DESC) rn
FROM employee e
INNER JOIN department d
ON e.DEPT_CD = d.DEPT_CD
WHERE d.DEPT_NAME = Dept_Name
) t
where rn = 1
END;
The problem with your current approach is not necessarily the WHERE clause, which should be working, but rather than you are using the RANK function. RANK would return 1 should two or more employees be tied for the highest salary.
By using ROW_NUMBER, you ensure that the subquery would only ever return a single row.
You are getting that error because of this part of the statement
select EMP_NAME,rank() over(order by salary desc) rn from employee.
You are pulling two columns one is EMP_NAME and other is rank() over(order by salary desc) rn.That's why you are getting that error.
Please modify the subquery such that you only select emp_name.
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
);
I am passing datatable as input parameter to stored procedure. Datatable contains id, Name,Lname,Mobileno,EmpId.
Employee table contains [Name],[Lname],[mobno],[Did] as columns.
When user is logged in, his Id come as DId. There are more than 1000 records. Instead of passing that id to datatable, I have created
separete parameter to sp. I want to add records to Employee table, which are not already exist. If combination of mobileno and Did already exists, then
don't insert into Employee table, else insert. Datatable may contain records, which can be duplicate. So I don't want to include that record. I want select only
distinct records and add them to table. I am intrested in mobile no. If there are 10 record having same moble no, I am fetching record, which comes first.
Following code is right or wrong. According to my knowledge, first from clause, then inner join, then where, then select execute. Record get fetched from datatable,
then inner join happens generate result, from that result not from datatable it will check record. So it will give me proper output.
Create Procedure Proc_InsertEmpDetails
#tblEmp EmpType READONLY,
#DId int
as
begin
INSERT INTO Employee
([Name],[Lname],[mobno],[Did])
SELECT [Name],[Lname],[mobno] #DId
FROM #tblEmp A
Inner join (
select min(Id) as minID, mobno from #tblEmp group by mobno
) MinIDTbl
on MinIDTbl.minID = A.ExcelId
WHERE NOT EXISTS (SELECT 1
FROM Employee B
WHERE B.[mobno] = A.[mobno]
AND B.[Did] = #DId )
end
or does I need to change like this
INSERT INTO Employee
([Name],[Lname],[mobno],[Did])
SELECT C.[Name],C.[Lname],C.[mobno], C.D_Id
from
(SELECT [Name],[Lname],[mobno] #DId as D_Id
FROM #tblEmp A
Inner join (
select min(Id) as minID, mobno from #tblEmp group by mobno
) MinIDTbl
on MinIDTbl.minID = A.ExcelId
)C
WHERE NOT EXISTS (SELECT 1
FROM Employee B
WHERE B.[mobno] = C.[mobno]
AND B.[Did] = #DId )
I am trying to use an Insert, Sequence and Select * to work together.
INSERT INTO BRK_INDV
Select * from (Select brk_seq.NEXTVAL as INDV_SEQ, a.*
FROM (select to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') BUSINESS_DAY, to_char(REQUEST_DATETIME,'hh24') src_hour,
CASE tran_type
WHEN 'V' THEN 'Visa'
WHEN 'M' THEN 'MasterCard'
ELSE tran_type
end text,
tran_type, count(*) as count
from DLY_STATS
where 1=1
AND to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') = '09-FEB-2015'
group by to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY'),to_char(REQUEST_DATETIME,'hh24'),tran_type order by src_hour)a);
This gives me the following error:
ERROR at line 2:
ORA-02287: sequence number not allowed here
I tried to remove the order by and still the same error.
However, if I only run
Select brk_seq.NEXTVAL as INDV_SEQ, a.*
FROM (select to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') BUSINESS_DAY, to_char(REQUEST_DATETIME,'hh24') src_hour,
CASE tran_type
WHEN 'V' THEN 'Visa'
WHEN 'M' THEN 'MasterCard'
ELSE tran_type
end text,
tran_type, count(*) as count
from DLY_STATS
where 1=1
AND to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY') = '09-FEB-2015'
group by to_date(to_char(REQUEST_DATETIME,'DD-MM-YYYY'),'DD-MM-YYYY'),to_char(REQUEST_DATETIME,'hh24'),tran_type order by src_hour)a;
It shows me proper entries. Then, why is select * not working for that?
Kindly help.
I see what you're trying to do. You want to insert rows into the BRK_INDV table in a particular order. The sequence number, which I assume will be the primary key of BRK_INDV, will be generated sequentially in the sorted order of the input rows.
You are working with a relational database. One of the first characteristics we all learn about a relational database is that the order of the rows in a table is insignificant. That's just a fancy word for fugitaboutit.
You cannot assume that a select * from table will return the rows in the same order they were written. It might. It might for quite a long time. Then something -- the number of rows, the grouping of some column values, the phase of the moon -- something will change and you will get them out in a seemingly totally random order.
If you want order, it must be imposed in the query, not the insert.
Here's the statement you should be executing:
INSERT INTO BRK_INDV
With
Grouped( Business_Day, Src_Hour, Text, Tran_Type, Count )As(
Select Trunc( Request_Datetime ) Business_Day,
To_Char( Request_Datetime, 'hh24') Src_Hour,
Case Tran_Type
When 'V' Then 'Visa'
When 'M' Then 'MasterCard'
Else Tran_Type
end Text,
Tran_Type, count(*) as count
from DLY_STATS
Where 1=1 --> Generated as dynamic SQL?
And Request_Datetime >= Date '2015-02-09'
And Request_Datetime < Date '2015-02-10'
Group By Trunc( Request_Datetime ), To_Char( Request_Datetime, 'hh24'), Tran_Type
)
Select brk_seq.Nextval Indv_Seq, G.*
from Grouped G;
Notice there is no order by. If you want to see the generated rows in a particular order:
select * from Brk_Indv order by src_hour;
Since there could be hundreds or thousands of transactions in any particular hour, you probably order by something other than hour anyway.
In Oracle, the trunc function is the best way to get a date with the time portion stripped away. However, you don't want to use it in the where clause (or, aamof, any other function such as to_date or to_char)as that would make the clause non-sargable and result in a complete table scan.
The problem is that you can't use a sequence in a subquery. For example, this gives the same ORA-02287 error you are getting:
create table T (x number);
create sequence s;
insert into T (select * from (select s.nextval from dual));
What you can do, though, is create a function that returns nextval from the sequence, and use that in a subquery:
create function f return number as
begin
return s.nextval;
end;
/
insert into T (select * from (select f() from dual));