SQL: How to use functions/operations in a where condition [duplicate] - sql

This question already has answers here:
How do I do top 1 in Oracle? [duplicate]
(9 answers)
Closed last year.
select * from mytable
where date = max(date)
This does not work. I hope my motive is clear. I know I can come around this problem by using sub-queries but is it possible to it without sub-query?

Subquery it is, unless you create your own function which can then be used directly in the WHERE clause. Otherwise, I don't think you can do that.
For example:
SQL> select ename, hiredate from emp order by hiredate desc;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983 --> that's the one we want
SCOTT 09.12.1982
MILLER 23.01.1982
FORD 03.12.1981
JAMES 03.12.1981
<snip>
The way you already know can be improved as following (why is it improved? Because it scans the table only once; your way, I presume, does it twice - once in a subquery (to find the MAX date value), and then once again in the main query):
SQL> with temp as
2 (select e.*,
3 rank() over (order by e.hiredate desc) rnk
4 from emp e
5 )
6 select ename, hiredate
7 from temp
8 where rnk = 1;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983
(Option which isn't that good):
SQL> select ename, hiredate
2 from emp
3 where hiredate = (select max(hiredate) from emp);
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983
SQL>
Or, with your own function:
SQL> create or replace function f_maxhire return date is
2 retval date;
3 begin
4 select max(hiredate) into retval from emp;
5 return retval;
6 end;
7 /
Function created.
SQL> select ename, hiredate
2 from emp
3 where hiredate = f_maxhire;
ENAME HIREDATE
---------- ----------
ADAMS 12.01.1983
SQL>

Related

Make a Report that Can Edit another Report in APEX

I have multiple report pages. I am working on a system that stores a lot of data. Is it possible to have reports that are able to have column edited on two different reports?
i.e. Not all the information may be relevant to the person entering data on the interactive grid. So I would have a condensed version of the report where the would be able to enter the data you are responsible for. But the report would still display the information entered on the condensed report version.
I'd think of a view and its instead of trigger.
Here's an example; see if you can adjust it to interactive grid. It is based on Scott's sample EMP and DEPT tables.
First, a view that joins these two tables:
SQL> create or replace view v_emp_dept as
2 select d.deptno, d.dname, e.empno, e.ename, e.job, e.sal
3 from emp e join dept d on d.deptno = e.deptno;
View created.
Instead of trigger; it fires when you update the view, but in the background it modifies data in view's underlying tables:
SQL> create or replace trigger trg_iu_ved
2 instead of update on v_emp_dept
3 for each row
4 begin
5 update dept d set
6 d.dname = :new.dname
7 where d.deptno = :new.deptno;
8
9 update emp e set
10 e.ename = :new.ename,
11 e.job = :new.job,
12 e.sal = :new.sal
13 where e.empno = :new.empno;
14 end;
15 /
Trigger created.
OK, let's test it. Sample data:
SQL> select * from v_emp_dept where deptno = 10;
DEPTNO DNAME EMPNO ENAME JOB SAL
---------- -------------- ---------- ---------- --------- ----------
10 ACCOUNTING 7782 CLARK MANAGER 2818
10 ACCOUNTING 7839 KING PRESIDENT 5750
10 ACCOUNTING 7934 MILLER CLERK 1495
Update some values:
SQL> update v_emp_dept set
2 dname = 'Accounting',
3 ename = initcap(ename),
4 sal = sal * 10
5 where deptno = 10;
3 rows updated.
Oracle says that 3 rows were updated:
SQL> select * from v_emp_dept where deptno = 10;
DEPTNO DNAME EMPNO ENAME JOB SAL
---------- -------------- ---------- ---------- --------- ----------
10 Accounting 7782 Clark MANAGER 28180
10 Accounting 7839 King PRESIDENT 57500
10 Accounting 7934 Miller CLERK 14950
The view looks OK. What about EMP and DEPT tables?
SQL> select * from dept where deptno = 10;
DEPTNO DNAME LOC
---------- -------------- -------------
10 Accounting NEW YORK
SQL> select empno, ename, job, sal from emp where deptno = 10;
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 Clark MANAGER 28180
7839 King PRESIDENT 57500
7934 Miller CLERK 14950
SQL>
Right; data has been changed in these two tables as well.

What is the best way(in case of performance) to find out the user details with maximum salary?

We have user details with a column of salary also, how can we print the user details with the maximum salary, I don't want to use the Subquery, and yeah how subquery will reduce the performance.
I know this query is wrong but I want something like this:
select User_name, user_id
from dual where salary=Max(salary);
Analytic functions help.
Using a CTE (which is kind of a subquery; don't be afraid of it, it doesn't bite and won't affect performance), query might look like this (based on sample Scott's schema):
SQL> select ename, sal from emp order by sal desc;
ENAME SAL
---------- ----------
KING 5000 --> this is the highest salary
FORD 3000 --> FORD and SCOTT share the 2nd place
SCOTT 3000
JONES 2975
BLAKE 2850
CLARK 2450
ALLEN 1600
TURNER 1500
MILLER 1300
WARD 1250 --> WARD and MARTIN are then 9th
MARTIN 1250
ADAMS 1100
JAMES 950
SMITH 800
14 rows selected.
Query is then
SQL> with temp as
2 (select ename,
3 dense_rank() over (order by sal desc) rnk
4 from emp
5 )
6 select ename
7 from temp
8 where rnk = 1;
ENAME
----------
KING
SQL>
Why dense_rank? Because two (or more) employees can have the same salary so they "rank" the same. For example, if you want to know whose salary is ranked as 9th, you'd
SQL> l8
8* where rnk = 1
SQL> c/1/9
8* where rnk = 9
SQL> /
ENAME
----------
WARD
MARTIN
SQL>
Query you suggested (although wrong, but - I got the idea) looks like this:
SQL> select ename
2 from emp
3 where sal = (select max(sal) from emp);
ENAME
----------
KING
SQL>
And yes, it affects performance because you're fetching data from the same emp table twice: once to find the max salary (in a subquery), and then in the main query to find who it belongs to.

Update table inside a loop in a procedure pl/sql

So i want to hire a new manager for a department. This procedure has 2 parameters, the department name i want to change, and the new id for manager (taken from employee's id). So to change it, i need to update all employee that has the old manager id, and change their manager id into the new one. This is my code so far, the problem is that it updated all of the employees' manager, so the whole database is updated. I must use procedure, not function. Any idea? Thanks.
CREATE OR REPLACE PROCEDURE update_manager(v_deptname IN departments.department_name%TYPE,v_empid IN employees.employee_id%TYPE) IS
v_deptid departments.department_id%type;
BEGIN
SELECT department_id INTO v_deptid FROM departments WHERE department_name=v_deptname;
FOR i IN (SELECT * FROM employees)
LOOP
IF i.department_id=v_deptid THEN
UPDATE employees
SET manager_id=v_empid
WHERE i.department_id=v_deptid;
END IF;
END LOOP;
END;
/
BEGIN
update_manager('Marketing',100);
END;
/
If you must create a PL/SQL procedure then you can do
CREATE OR REPLACE PROCEDURE update_manager(v_deptname IN departments.department_name%TYPE,v_empid IN employees.employee_id%TYPE) IS
v_deptid departments.department_id%type;
BEGIN
SELECT department_id INTO v_deptid FROM departments WHERE department_name=v_deptname;
UPDATE employees
SET manager_id=v_empid
WHERE department_id=v_deptid;
END;
/
The problem with your code that's causing "it updated all of the employees' manager" is that your update statement:
UPDATE employees
SET manager_id=v_empid
WHERE i.department_id=v_deptid;
Your filter here is comparing i.department_id, this is the variable that's coming from your FOR i IN (SELECT * FROM employees), NOT from the update statement. You've already confirmed that i.department_id=v_deptid because you are calling this in a loop with an if statement checking it.
It is not efficient at all to get all rows in employees, loop the results, checking each row if it matches a condition and then firing off an update statement (even if your update statement is filtering against the correct row.
I don't have your tables, but I do have Scott's so - here's an example.
Departments and employees in department 10:
SQL> select * From dept order by deptno;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7839
10 7839 KING
10 7934 MILLER 7782
Procedure that looks like yours (as you're studying PL/SQL), using a variable to fetch department number into, as well as a loop:
SQL> create or replace procedure update_manager
2 (v_deptname in dept.dname%type,
3 v_empno in emp.empno%type
4 )
5 is
6 v_deptno dept.deptno%type;
7 begin
8 select d.deptno
9 into v_deptno
10 from dept d
11 where d.dname = v_deptname; --> this is what you are missing
12
13 for cur_r in (select e.empno
14 from emp e
15 where e.deptno = v_deptno
16 )
17 loop
18 update emp a set
19 a.mgr = v_empno
20 where a.empno = cur_r.empno;
21 end loop;
22 end;
23 /
Procedure created.
Testing: let's modify manager of all employees in department 10:
SQL> exec update_manager ('ACCOUNTING', 7839);
PL/SQL procedure successfully completed.
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7839
10 7839 KING 7839
10 7934 MILLER 7839
SQL>
Seems to work. Though, that's inefficient; doing it row-by-row (in a loop) is usually slow-by-slow. Work on the whole sets of data, rather. Something like this:
SQL> rollback;
Rollback complete.
SQL> create or replace procedure update_manager
2 (v_deptname in dept.dname%type,
3 v_empno in emp.empno%type
4 )
5 is
6 begin
7 update emp e set
8 e.mgr = v_empno
9 where e.deptno = (select d.deptno
10 from dept d
11 where d.dname = v_deptname
12 );
13 end;
14 /
Procedure created.
SQL> exec update_manager ('ACCOUNTING', 7782);
PL/SQL procedure successfully completed.
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7782
10 7839 KING 7782
10 7934 MILLER 7782
SQL>
Why not a simple select for manager id and an update on all concerned employee ?
DECLARE #department_id uniqueidentifier
SELECT #department_id = departement_id
FROM departments
WHERE department_name = #v_deptname
UPDATE employees
SET manager_id = #v_empid
WHERE department_id = #department_id

Oracle- sql query to print odd number of rows when we do not have number data type columns

I was trying to print odd numbers of rows from my table without taking taking help of my numeric cloumns
when I try to execute this query I was getting only first row.
select * from emp3 where mod(rownum,2)=1;
emp3 is my table name.
and when I use my one of the numeric columns in place of rownum I was getting desired output.
select * from emp3 where mod(eid,2)=1 order by eid;
where eid is a numeric column in the table.
But what if do not have a numeric column and I want to print only odd number of rows from the table?
Help me!
Try to execute the below query
select * from (select rownum rn ,column from column_name) where mod(rn,2) <> 0
and please refer to this link for better understanding the concept of rownum https://www.youtube.com/watch?v=QMyw1jumGyQ
If the EID column isn't numeric, then use something that is. For example, ROW_NUMBER gives such an information:
SQL> with temp as
2 (select empno, ename, job sal,
3 row_number() over (order by null) rn
4 from emp
5 )
6 select *
7 from temp
8 where mod(rn, 2) = 1;
EMPNO ENAME SAL RN
---------- ---------- --------- ----------
7369 SMITH CLERK 1
7521 WARD SALESMAN 3
7654 MARTIN SALESMAN 5
7782 CLARK MANAGER 7
7839 KING PRESIDENT 9
7876 ADAMS CLERK 11
7902 FORD ANALYST 13
7 rows selected.
SQL>
Or even ROWNUM you already tried to use:
SQL> with temp as
2 (select empno, ename, job sal,
3 rownum rn
4 from emp
5 )
6 select *
7 from temp
8 where mod(rn, 2) = 1;
EMPNO ENAME SAL RN
---------- ---------- --------- ----------
7369 SMITH CLERK 1
7521 WARD SALESMAN 3
7654 MARTIN SALESMAN 5
7782 CLARK MANAGER 7
7839 KING PRESIDENT 9
7876 ADAMS CLERK 11
7902 FORD ANALYST 13
7 rows selected.
SQL>

How to SUM two highest salary on table EMP on deptno ( deptno 10,20,30) using BREAK and COMPUTE SUM?

This is my code:
BREAK ON DEPTNO SKIP 1
compute sum of sal on deptno
SELECT deptno, empno, ename,sal FROM
(SELECT deptno, empno, ename, sal FROM emp group by deptno, empno, ename, sal order by DEPTNO)
WHERE ROWNUM <= 2;
But the result is:
DEPTNO EMPNO ENAME SAL
---------- ---------- ---------- ----------
10 7782 CLARK 2450
7839 KING 5000
********** ----------
sum 7450
What is good, but I want to get also on deptno 20, deptno 30:
(This is expected result, all in the same return - for deptno 10, 20,30)
DEPTNO EMPNO ENAME SAL
---------- ---------- ---------- ----------
10 7782 CLARK 2450
7839 KING 5000
********** ----------
sum 7450
DEPTNO EMPNO ENAME SAL
---------- ---------- ---------- ----------
20 7788 SCOTT 3000
7902 FORD 3000
7566 JONES 2975
********** ----------
sum 8975
DEPTNO EMPNO ENAME SAL
---------- ---------- ---------- ----------
30 7698 BLAKE 2850
7499 ALLEN 1600
********** ----------
sum 4450
My question is how to sum two highest salary on table EMP on deptno (deptno 10,20,30) using BREAK and COMPUTE SUM all in one return (just like expected above)?
I think my code is closely good, but is missing something.
Sorry to say, your query doesn't make much sense. You select from emp and group by empno (and other columns), so you say: "Give me data from emp, but make it one record per empno", which means no grouping at all (as there is exactly one record per empno in emp).
Your subquery
SELECT deptno, empno, ename, sal
FROM emp
group by deptno, empno, ename, sal
order by DEPTNO
is the same as
SELECT deptno, empno, ename, sal
FROM emp
order by DEPTNO
Then you take the two first rows after having the records ordered by deptno, so you get two emp records arbitrarily chosen for the first deptno.
What you want instead is show emp records (i.e. no group by) with the two highest salaries per dept. You can rank salaries per departmnent with the analytic function DENSE_RANK. Then stay with the records ranked 1 and 2.
select deptno, empno, ename, sal
from
(
select
deptno, empno, ename, sal,
dense_rank() over (partition by deptno order by sal desc) as rnk
from emp
)
where rnk <= 2
order by deptno, sal desc;