Can somebody please push me in right direction [closed] - sql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
My code below to delete duplicate entries
with mytable as
(
select a.*,
row_number() over(partition by emp_id order by emp_id) as Row_number
from employee_Details2 a
)
delete from mytable
where Row_number=2;
Error:
*ORA-00928: missing SELECT keyword
00928. 00000 - "missing SELECT keyword"
*Cause:
*Action:
Error at Line: 43 Column: 1*

You can't delete from a CTE.
If it is about deleting from employee_details2, then see if this helps:
delete from employee_details2 a
where a.rowid > (select min(b.rowid)
from employee_details2 b
where b.empid = a.empid
);
If you want to delete rows using "row number" (as you put it), well - yes, you can do that, but what I suggested originally is simpler and better.
SQL> select * From test order by deptno;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 06/09/1981 2450 10
7839 KING PRESIDENT 11/17/1981 10000 10
7934 MILLER CLERK 7782 01/23/1982 1300 10
7566 JONES MANAGER 7839 04/02/1981 2975 20
7902 FORD ANALYST 7566 12/03/1981 3000 20
7876 ADAMS CLERK 7788 01/12/1983 1300 20
7369 SMITH CLERK 7902 12/17/1980 920 20
7788 SCOTT ANALYST 7566 12/09/1982 3000 20
7521 WARD SALESMAN 7698 02/22/1981 1250 500 30
7844 TURNER SALESMAN 7698 09/08/1981 1500 0 30
7499 ALLEN SALESMAN 7698 02/20/1981 1600 300 30
7900 JAMES CLERK 7698 12/03/1981 950 30
7698 BLAKE MANAGER 7839 05/01/1981 2850 30
7654 MARTIN SALESMAN 7698 09/28/1981 1250 1400 30
14 rows selected.
SQL> delete from test b
2 where b.empno in (select c.empno
3 from (select a.empno, row_number() over (partition by a.deptno order by a.empno) rn
4 from test a
5 ) c
6 where c.rn > 1
7 );
11 rows deleted.
SQL> select * From test order by deptno;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 06/09/1981 2450 10
7369 SMITH CLERK 7902 12/17/1980 920 20
7499 ALLEN SALESMAN 7698 02/20/1981 1600 300 30
SQL>

The delete can not work with CTE, You need to separate the logic of DELETE and SELECT as follows:
delete from employee_Details2
where rowid in
(select rowid from
( select a.rowid, a.*,
row_number() over(partition by emp_id order by emp_id) as Row_number
from employee_Details2 a )
where Row_number = 2)

Related

One query with combining data from two tables and grouping (sql, oracle)

I am trying to write which create a view that shows the number of departments for each location (Loc) and the number of employees working in the departments in one query.
Tables are provided below.
Table Dept
DEPTNO
DNAME
LOC
10
ACCOUNTING
NEW YORK
20
RESEARCH
DALLAS
30
SALES
CHICAGO
40
OPERATIONS
BOSTON
50
RISK
BOSTON
Table Emp
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
9901 Kowalski SALESMAN 9345 23-JAN-90 300 100 12
7369 SMITH CLERK 7902 17-MAR-80 15355.58 20
7499 ALLEN SALESMAN 7698 20-MAR-81 1600 300 30
7521 WARD SALESMAN 7698 22-MAR-81 1250 500 30
7566 JONES MANAGER 7839 02-MAR-81 57103.26 20
7654 MARTIN SALESMAN 7698 28-MAR-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAR-81 2850 30
7782 CLARK MANAGER 7839 09-MAR-81 2450 10
7788 SCOTT ANALYST 7566 09-MAR-82 57583.15 20
7839 KING PRESIDENT 17-MAR-81 5000 10
7844 TURNER SALESMAN 7698 08-MAR-81 1500 0 30
7876 ADAMS CLERK 7788 12-MAR-83 21113.82 20
7900 JAMES CLERK 7698 03-MAR-81 950 30
7902 FORD ANALYST 7566 03-MAR-81 57583.15 20
7934 MILLER CLERK 7782 23-MAR-82 1300 10
The output should be
LOC NO_DEP NO_EMP
------------- ---------- ----------
NEW YORK 1 3
DALLAS 1 1
CHICAGO 1 5
BOSTON 2 6
So far I was able to create two separate queries which are provided below and which gives this result however I must do it in a one query which will give output in one table.
SELECT DISTINCT d.Loc, Count(d.Deptno) AS No_Dep, d.Deptno
FROM Dept d
GROUP BY d.Loc, d.Deptno;
SELECT DISTINCT COUNT(e.Empno) OVER (PARTITION BY e.Deptno) AS No_Emp, e.Deptno
FROM Emp e;
Correct solution is
SELECT d.LOC, COUNT(DISTINCT d.DEPTNO) AS NO_DEP, COUNT(e.EMPNO) AS NO_EMP
FROM DEPT d
LEFT JOIN EMP e ON d.DEPTNO = e.DEPTNO
GROUP BY D.LOC;
You first need to join them and then and then need to do the aggregation -
SELECT D.LOC, COUNT(DISTINCT DEPTNO) NO_DEP, COUNT(EMPNO) NO_EMP
FROM DEPT D
JOIN EMP E ON D.DEPTNO = E.DEPTNO
GROUP BY D.LOC;
you can use "group by" and "having" to solve such queries
The HAVING clause is used instead of WHERE with aggregate functions. While the GROUP BY Clause groups rows that have the same values into summary rows. The having clause is used with the where clause in order to find rows with certain conditions. The having clause is always used after the group By clause.
example:
select D.LOC AS LOC, COUNT(D.DEPTNO) AS NO_DEP, COUNT(distinct E.EMPNO) AS NO_EMP
from Dept D, Emp E
where D.DEPTNO=E.DEPTNO
group by D.LOC;

How can we handle multiple rows returned from a subquery inside a case statement?

Write a SELECT query if department id provided at run time is 100 then it will return all the employee details those belong to department 100 else i wanted to print all the employees from employee table.
i have written below query :-
SELECT * FROM EMPLOYEES
WHERE department_id IN (
CASE &InputDept
WHEN 100 THEN 100
ELSE (SELECT DISTINCT department_id FROM DEPARTMENTS) END ) ;
This works fine when input is 100 but returned "Single-row subquery returns more than one row".
i understand that case is a statement which works like "if-then-else" with single value, also i tried with decode but no luck.
This can be easily done in PL/SQL , but is this possible with SELECT query?
Skip the case expression, use regular AND/OR instead:
SELECT * FROM EMPLOYEES
WHERE (&InputDept = 100 and department_id = 100)
OR (&InputDept <> 100 and department_id IN
(SELECT department_id FROM DEPARTMENTS)) ;
To handle NULL InputDept:
SELECT * FROM EMPLOYEES
WHERE (&InputDept = 100 and department_id = 100)
OR ((&InputDept <> 100 or &InputDept IS NULL) AND department_id IN
(SELECT department_id FROM DEPARTMENTS)) ;
It's better to use a query like this:
SELECT * FROM EMPLOYEES
WHERE department_id = CASE &InputDept WHEN 100 THEN 100 END
UNION ALL
SELECT * FROM EMPLOYEES
WHERE (&InputDept != 100 or &InputDept is NULL)
AND department_id IN (SELECT DISTINCT department_id FROM DEPARTMENTS);
Do not forget about employees without department_id, ie NULLs.
This query will be optimized by optimizer at runtime to do not execute unneeded part of union all
Yet another alternative (example based on Scott's sample schema, ran in SQL*Plus):
SQL> select *
2 from emp
3 where deptno in (select deptno from dept
4 where &&inputdept <> 10
5 union all
6 select &&inputdept from dual
7 where &&inputdept = 10
8 );
Enter value for inputdept: 10
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09.06.81 2450 10
7839 KING PRESIDENT 17.11.81 5000 10
7934 MILLER CLERK 7782 23.01.82 1300 10
SQL> undefine inputdept
SQL> /
Enter value for inputdept: 25
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17.12.80 800 20
7499 ALLEN SALESMAN 7698 20.02.81 1600 300 30
7521 WARD SALESMAN 7698 22.02.81 1250 500 30
7566 JONES MANAGER 7839 02.04.81 2975 20
7654 MARTIN SALESMAN 7698 28.09.81 1250 1400 30
7698 BLAKE MANAGER 7839 01.05.81 2850 30
7782 CLARK MANAGER 7839 09.06.81 2450 10
7788 SCOTT ANALYST 7566 09.12.82 3000 20
7839 KING PRESIDENT 17.11.81 5000 10
7844 TURNER SALESMAN 7698 08.09.81 1500 0 30
7876 ADAMS CLERK 7788 12.01.83 1100 20
7900 JAMES CLERK 7698 03.12.81 950 30
7902 FORD ANALYST 7566 03.12.81 3000 20
7934 MILLER CLERK 7782 23.01.82 1300 10
14 rows selected.
SQL>

Calculate sum of current row and child rows in hierarchical WITH query in Oracle

I have a Oracle hiƫrarchical query using a WITH clause (common table expression). My goal is to calculate the sum of the salaries of the current row and the child rows. I've come up with the following, using a correlated subquery between a cte that calculates the sum of every row of which the current name is part of one of the paths of the previous cte. Is there a better way to do this?
with managers(empno, mgr, ename, sal, path) as (
select empno, mgr, ename, sal, '/'||empno
from emp
where mgr is null
union all
select e.empno, e.mgr, e.ename, e.sal, m.path||'/'||e.empno
from emp e join managers m
on (e.mgr = m.empno)
)
search depth first by ename set search_order
, totals(empno, total) as (
select e.empno, (select sum(sal)
from managers
where regexp_like( path||'/', '/'||e.empno||'/'))
from emp e)
select empno, mgr, ename, sal, path, total
from managers join totals using (empno)
order by search_order;
Result:
EMPNO MGR ENAME SAL PATH TOTAL
--------- --------- ----------- --------- -------------------- ---------
7839 KING 5000 /7839 29025
7698 7839 BLAKE 2850 /7839/7698 9400
7499 7698 ALLEN 1600 /7839/7698/7499 1600
7900 7698 JAMES 950 /7839/7698/7900 950
7654 7698 MARTIN 1250 /7839/7698/7654 1250
7844 7698 TURNER 1500 /7839/7698/7844 1500
7521 7698 WARD 1250 /7839/7698/7521 1250
7782 7839 CLARK 2450 /7839/7782 3750
7934 7782 MILLER 1300 /7839/7782/7934 1300
7566 7839 JONES 2975 /7839/7566 10875
7902 7566 FORD 3000 /7839/7566/7902 3800
7369 7902 SMITH 800 /7839/7566/7902/7369 800
7788 7566 SCOTT 3000 /7839/7566/7788 4100
7876 7788 ADAMS 1100 /7839/7566/7788/7876 1100
An efficient, pure SQL approach uses a connect by query to assign levels in the hierarchy, followed by a carefully constructed match_recognize operation. Once we have the nodes in the proper hierarchical order and with levels assigned, the descendants of any node can be recognized by a continuous sequence of nodes with levels strictly greater than the level of the starting node. match_recognize can identify such sequences efficiently, in a single pass over the rows.
This approach avoids the much more costly generation of multiple rows, followed by aggregation.
Possible downside: this will only work in Oracle 12.1 and higher - match_recognize does not exist in Oracle 11.2 and earlier. It is also supported only by a limited number of database products (including Oracle db), even though it is part of the SQL Standard, not a proprietary extension.
The solution looks something like this (using the standard scott.emp table):
with
first_pass as (
select empno, ename, mgr, sal, rownum as ord, level as lvl
from scott.emp
start with mgr is null
connect by mgr = prior empno
order siblings by ename -- Not sure why you are doing it this way
)
select empno, ename, mgr, sal, total_sal
from first_pass
match_recognize (
order by ord
measures x.empno as empno, x.ename as ename, x.mgr as mgr, x.sal as sal,
sum(sal) as total_sal
after match skip to next row
pattern ( x y* )
define y as lvl > x.lvl
);
Output:
EMPNO ENAME MGR SAL TOTAL_SAL
---------- ---------- ---------- ---------- ----------
7839 KING 5000 29025
7698 BLAKE 7839 2850 9400
7499 ALLEN 7698 1600 1600
7900 JAMES 7698 950 950
7654 MARTIN 7698 1250 1250
7844 TURNER 7698 1500 1500
7521 WARD 7698 1250 1250
7782 CLARK 7839 2450 3750
7934 MILLER 7782 1300 1300
7566 JONES 7839 2975 10875
7902 FORD 7566 3000 3800
7369 SMITH 7902 800 800
7788 SCOTT 7566 3000 4100
7876 ADAMS 7788 1100 1100
A database approach would be a recursive CTE that first list all emps along their salaries.
In the recursive part you add for each empa new record with the related person (REL_EMPNO) for all their direct and indirect managers.
You get something like this (I'm using only subset of the data)
with dt(EMPNO, REP_EMPNO, REL_ENAME, SAL, MGR) as (
select EMPNO, EMPNO, ENAME, SAL, MGR
from emp
union all
select dt.EMPNO, emp.EMPNO, emp.ENAME, dt.SAL, emp.MGR
from emp
join dt on emp.empno = dt.mgr
)
select
EMPNO, REP_EMPNO, REL_ENAME, SAL
from dt order by 1,2;
EMPNO REP_EMPNO REL_EN SAL
---------- ---------- ------ ----------
7499 7499 ALLEN 1600
7499 7698 BLAKE 1600
7499 7839 KING 1600
7654 7654 MARTIN 1250
7654 7698 BLAKE 1250
7654 7839 KING 1250
7698 7698 BLAKE 2850
7698 7839 KING 2850
7839 7839 KING 5000
7900 7698 BLAKE 950
7900 7839 KING 950
7900 7900 JAMES 950
So for example you see that the salary of ALLEN (1600) is related to him and also to BLAKE and KING.
This is nearly done only remains to group by the related person and sum the salary.
with dt(EMPNO, REP_EMPNO, REL_ENAME, SAL, MGR) as (
select EMPNO, EMPNO, ENAME, SAL, MGR
from emp
union all
select dt.EMPNO, emp.EMPNO, emp.ENAME, dt.SAL, emp.MGR
from emp
join dt on emp.empno = dt.mgr
)
select
REP_EMPNO EMPNO, REL_ENAME ENAME, sum(SAL) SAL
from dt
group by REP_EMPNO, REL_ENAME
order by 1;
EMPNO ENAME SAL
---------- ------ ----------
7499 ALLEN 1600
7654 MARTIN 1250
7698 BLAKE 6650
7839 KING 11650
7900 JAMES 950
If on the contrary you have a more program developer background you may find usefull approach in defining a recursive PL/SQL function, that returns the salary of a person if the person have no childs. Otherwise the function calls itself recursively to add the sum of salaries of all the childs.
create or replace function get_hir_sal(p_empno int) return number as
v_tot_child int;
v_own_sal NUMBER;
v_child_sal NUMBER;
begin
/* check if child noted exists */
select count(*) into v_tot_child
from emp
where mgr = p_empno;
/* own salary */
select sal into v_own_sal
from emp
where empno = p_empno;
if v_tot_child = 0 then
return(v_own_sal);
else
select sum(get_hir_sal(empno))
into v_child_sal
from emp
where mgr = p_empno;
return(v_own_sal + v_child_sal);
end if;
end;
/
You call it with following query to get an identical result
select EMPNO, MGR, ENAME, SAL,
get_hir_sal(EMPNO) total
from emp
order by 1;

in oracle data sample, Deal with Minimum salary problem per group(when duplicates exist)

emp
empno ename job mgr hiredate sal comm deptno
7369 SMITH CLERK 7902 80/12/17 800 null 20
7499 ALLEN SALESMAN 7698 81/02/20 1600 300 30
7521 WARD SALESMAN 7698 81/02/22 1250 500 30
7566 JONES MANAGER 7839 81/04/02 2975 null 20
7654 MARTIN SALESMAN 7698 81/09/28 1250 1400 30
7698 BLAKE MANAGER 7839 81/05/01 2850 null 30
7782 CLARK MANAGER 7839 81/06/09 2450 null 10
7788 SCOTT ANALYST 7566 82/12/09 3000 null 20
7839 KING PRESIDENT null 81/11/17 5000 null 10
7844 TURNER SALESMAN 7698 81/09/08 1500 null 30
7876 ADAMS CLERK 7788 83/01/12 1100 null 20
7900 JAMES CLERK 7698 81/12/03 950 null 30
7902 FORD ANALYST 7566 81/12/03 3000 null 20
7934 MILLER CLERK 7782 82/01/23 1300 null 10
Each manager needs to find the employee with the smallest salary among other employees. However, the minimum annual salary is over 1000.
I tried
select e.ename, e.sal, e.mgr
from (select ename, sal, mgr from emp where sal > 1000) e
where (e.sal, mgr) in (select min(sal), mgr from emp group by mgr) and mgr is not null
order by sal desc;
and it`s result
ename sal mgr
SCOTT 3000 7566
FORD 3000 7566
CLARK 2450 7839
MILLER 1300 7782
ADAMS 1100 7788
As you can see, among the employees with 7698 managers, salary must be over 1000, but min is less than 1000, so it is excluded when executed with my code.
result what I want
ename sal mgr
SCOTT 3000 7566
FORD 3000 7566
CLARK 2450 7839
MILLER 1300 7782
WARD 1250 7698
MARTIN 1250 7698
ADAMS 1100 7788
I want the final output value to be in descending order based on salary.
What should I change in the code?
*in my oracle version -> oracle 11g
select ename, sal, mgr from emp
where (sal, mgr) in (
select min(case when sal >= 1000 then sal end), mgr
from emp group by mgr
)
and mgr is not null
order by sal desc;
MIN() aggregate function can accept CASE expressions. Here, we are nullifying any values less then 1000. The MIN() will return a least NON-NULL value as long as there is at least one value that is 1000 or above for a given group.
You may also do the following, MIN() analytic function.
select * from (
select emp.*, min(case when sal >= 1000 then sal end) over (partition by mgr) mn
from emp
)
where mgr is not null and sal = mn
order by sal desc;

Compare columns in 2 Oracle 11g tables

I have 2 tables in Oracle 11g one containing the actual source data and the other masked data.
The table has 1260000 rows approximately.
The column I am comparing is Varchar.
I am trying to check if all the data has been masked to value different from the original data.
I am performing a select query with 2 conditions on the join 1. with a unique row identifier 2. the value I am trying to compare.
The query never returns upon execution.
Is there a way to get an output where I can see the rows where the value is the same in both the table ?
This is how I understood the question.
For example, I created a "duplicate" of Scott's EMP table and "masked" names:
SQL> create table emp2 as select * From emp;
Table created.
SQL> update emp2 set ename = translate(ename, 'AEIOU', '98765');
14 rows updated.
SQL> select empno, ename from emp2 order by empno;
EMPNO ENAME
---------- ----------
7369 SM7TH
7499 9LL8N
7521 W9RD
7566 J6N8S
7654 M9RT7N
7698 BL9K8
7782 CL9RK
7788 SC6TT
7839 K7NG
7844 T5RN8R
7876 9D9MS
7900 J9M8S
7902 F6RD
7934 M7LL8R
14 rows selected.
To check which names are different:
SQL> select a.empno, a.ename, b.ename
2 from emp a join emp2 b on a.empno = b.empno
3 where a.ename <> b.ename
4 order by a.empno;
EMPNO ENAME ENAME
---------- ---------- ----------
7369 SMITH SM7TH
7499 ALLEN 9LL8N
7521 WARD W9RD
7566 JONES J6N8S
7654 MARTIN M9RT7N
7698 BLAKE BL9K8
7782 CLARK CL9RK
7788 SCOTT SC6TT
7839 KING K7NG
7844 TURNER T5RN8R
7876 ADAMS 9D9MS
7900 JAMES J9M8S
7902 FORD F6RD
7934 MILLER M7LL8R
14 rows selected.
SQL>
Or, in your case, as you'd want to see values that aren't changed, you'd modify line #3 to
where a.ename = b.ename