replace WITH clause with VIEW in Oracle - sql

I have a query like this.
with test1 as (
select emp.empno, emp.deptno, emp.name, emp.hiredate, dept.deptname
from emp, dept
where
emp.deptno = dept.deptno
and emp.deptno = 72
and emp.salary > 5000
)
select inner1.*
from (
select 'abc' as title,
1 emp_order,
name, hiredate, deptname
from test1
UNION ALL
select 'xyz' as title,
2 emp_order,
name, hiredate, deptname
from test1
) inner1
I am trying to remove the WITH clause completely and create a VIEW instead.The only problem I have is the dynamic value in the WITH clause.
I tried this:
CREATE VIEW testview as
select emp.empno, emp.deptno, emp.name, emp.hiredate, dept.deptname
from emp, dept
where
emp.deptno = dept.deptno
and emp.deptno = 72
and emp.salary > 5000
Updated query
select inner1.*
from (
select 'abc' as title,
1 emp_order,
name, hiredate, deptname
from testview
UNION ALL
select 'xyz' as title,
2 emp_order,
name, hiredate, deptname
from testview
) inner1
In this case how can I pass bind values for salary and deptno cols in the view?

If I understand correctly then you want to use parameters in your view.
To do this you can use either of the below approaches:
a.) If your parameters are more or less static and not changing frequently you can use a Table to store the value of this parameter. The values in this table can be updated by using a PL/SQL package or procedure. This table can then be refrenced in your view.
b.) If you require different parameter values for different sessions you can also Try using Application Context as given here
Hope it helps
Vishad

You do not need to remove the common table expression in order to use this query in a view. If you remove the predicates and apply them to a query against the view they can pushed inside the CTE anyway.
e.g.
CREATE VIEW testview as
select emp.empno, emp.deptno, emp.name, emp.hiredate, dept.deptname, emp.salary
from emp join dept ON emp.deptno = dept.deptno;
select inner1.*
from (
select 'abc' as title,
1 emp_order,
name, hiredate, deptname
from testview
where deptno = 72 and salary > 5000
UNION ALL
select 'xyz' as title,
2 emp_order,
name, hiredate, deptname
from testview
where deptno = 72 and salary > 5000
) inner1

Related

Why are these 2 queries giving different outputs?

Query no 1:-
SELECT COUNT(ENAME)
FROM EMP
WHERE
JOB IN 'MANAGER'
OR JOB IN 'ANALYST'
AND SAL IN (
SELECT SAL + NVL (COMM,0)
FROM EMP
WHERE SAL LIKE '%0')
GROUP BY JOB;
The Query 1 gives me the following output:-
COUNT(ENAME)
------------
3
2
Query no 2:-
SELECT COUNT(ENAME)
FROM EMP
WHERE
JOB = ANY (
SELECT JOB
FROM EMP
WHERE JOB IN ('MANAGER', 'ANALYST')
)
AND SAL IN (
SELECT SAL + NVL (COMM,0)
FROM EMP
WHERE SAL LIKE '%0'
)
GROUP BY JOB;
The Query 2 gives me the following output:-
COUNT(ENAME)
------------
2
2
Summary: The AND operator has higher precedence than the OR operator. To fix it, use brackets around the OR expression.
AND has higher precedence than OR so your first query is the equivalent of (with added brackets and indentation to emphasise the precedence):
SELECT COUNT(ENAME)
FROM EMP
WHERE JOB IN 'MANAGER'
OR ( JOB IN 'ANALYST'
AND SAL IN (
SELECT SAL + NVL (COMM,0)
FROM EMP
WHERE SAL LIKE '%0'
)
)
GROUP BY JOB;
So you are finding either: managers with any salary; or analysts whose salary equals the salary plus commission of any other employee.
Your second query is the equivalent of:
SELECT COUNT(ENAME)
FROM EMP
WHERE ( JOB IN 'MANAGER'
OR JOB IN 'ANALYST'
)
AND SAL IN (
SELECT SAL + NVL (COMM,0)
FROM EMP
WHERE SAL LIKE '%0'
)
GROUP BY JOB;
Which finds: any employee who is either a manager or an analyst and whose salary equals the salary plus commission of any other employee.
You could rewrite your first query to:
SELECT COUNT(ENAME)
FROM EMP
WHERE JOB IN ('MANAGER', 'ANALYST')
AND SAL IN (
SELECT SAL + NVL (COMM,0)
FROM EMP
WHERE SAL LIKE '%0'
)
GROUP BY JOB;
JOB IN 'MANAGER' OR ...
means all managers OR the next predicate
please put parentheses to achieve the expected result
JOB IN ('MANAGER', 'ANALYST')

How to deal with the "missing right parenthesis " in Oracle 19c?

I have written a piece of code in Oracle database 19c to extract the salary of employees who earns more than the average salary of their respective departments but the query shows "missing right parenthesis". Can anybody help me with it, like where and what the error is all about?
select ename
from emp
where sal > (select round(avg(sal)) as avg_sal, deptno
from emp
group by deptno
order by 2);
You can use these queries to get job done.
select ename
from emp e1
where sal > (select round(avg(sal)) as avg_sal
from emp e2
where e2.deptno = e1.deptno
);
or
select e1.ename
from emp e1,
(
select round(avg(sal)) as avg_sal, deptno
from emp
group by deptno
) e2
where e1.deptno = e2.deptno
and e1.sal > avg_sal
;
Now the output of your subquery is 2 columns and multiple rows, which is against the business logic you described.
To gain the desired result you need to modify the query a bit:
remove the second column in subquery;
remove GROUP BY and ORDER BY clauses from the subquery.
Should be as follows:
SELECT ename FROM emp WHERE sal > (SELECT round(AVG(sal)) AS avg_sal FROM emp);
Now subquery returns only one values which is valuated with each salary in the main query and returns employees' names of ones, that have salary more that average.
The "missing right parenthesis" error is that the ORDER BY clause is not allowed in the sub-query so the SQL parser expects the query to end after the GROUP BY clause.
Once you fix that you get a further error that the sub-query will return multiple rows:
select ename
from emp
where sal > (select round(avg(sal)) as avg_sal, deptno
from emp
group by deptno);
Outputs a different error message:
ORA-00913: too many values
To fix that you need to restrict the sub-query to a single row and correlate the outer-query to the sub-query:
select ename
from emp e
where sal > (select round(avg(sal)) as avg_sal
from emp a
WHERE e.deptno = a.deptno);
Which, for the sample data:
CREATE TABLE emp (ename, sal, deptno) AS
SELECT 'Alice', 100, 1 FROM DUAL UNION ALL
SELECT 'Beryl', 200, 1 FROM DUAL UNION ALL
SELECT 'Carol', 300, 1 FROM DUAL UNION ALL
SELECT 'Debra', 100, 2 FROM DUAL UNION ALL
SELECT 'Ester', 200, 2 FROM DUAL;
Outputs:
ENAME
Carol
Ester
If you want to do it in a single table-scan then you can use analytic functions:
SELECT ename
FROM (
SELECT ename,
sal,
ROUND(AVG(sal) OVER (PARTITION BY deptno)) AS avg_sal
FROM emp
)
WHERE sal > avg_sal;
db<>fiddle here

using set operator query to find the result of the given questions

Using set operator display the DEPTNO,SUM(SAL) for each dept, JOB,SUM(SAL) for each Job and Total Salary.
Using Set Operator display the JOB and Deptno in employees working in deptno 20,10,30 in that order.
for first question my query is this:
select e.deptno,to_char(null),e.sum(sal),
from emp e
UNION
select d.deptno,d.job,d.sum(sal)
from emp d
group by deptno,job;
I have no idea how to do the second one.
SET opetator can be union,intersection,minus...
The phrasing of the first question is not 100% clear to me, but the query should be:
select deptno, '', sum(sal) from emp group by deptno
union
select deptno, job, sum(sal) from emp group by deptno, job
For the second question you can use UNION again. For example:
select job, deptno
from (
select job, deptno, 2 as o from emp where deptno = 10
union
select job, deptno, 1 from emp where deptno = 20
union
select job, deptno, 3 from emp where deptno = 30
) x
order by o

GROUP BY - inline view and unjoined tables

I have the following ORACLE query where I attempt to find the department with the highest average salary. I would like to use in-line view (i.e. retain the b dataset) for this implementation, but struggle to get the right part at the WHERE and GROUP BY components. I know the below GROUP BY and WHERE (which is non-existant) is wrong. But how do i correct them?
select a.deptno from emp a,
(select max(avg_sal) max_avg_sal from (select
avg(sal) avg_sal from emp group by deptno) ) b
group by a.deptno, b.max_avg_sal
having avg(a.sal) = b.max_avg_sal
Expected Result
deptno
10
Emp Structure
deptno staff sal
10 A 1000
10 B 1500
11 C 1100
12 D 1000
12 E 900
12 F 1000
Is this what you want?
select e.*
from (select e.*, avg(e.salary) over (partition by e.deptno) as avg_salary
from emp e
) e
order by avg_salary desc
fetch first 1 row only;
fetch first is available in Oracle 12c+. You can do similar things with an additional subquery in earlier versions.
You can use subquery
select deptno from tablename
group by deptno
having avg(sal)= (select max(asal) from (select avg(sal) as asal from tablename group by deptdno)A)
The straight-forward way is:
select deptno
from emp
group by deptno
order by avg(salary) desc
fetch first row with ties;
FETCH FIRST is available as of Oracle 12c.
In Oracle 11g we could use this instead:
select deptno
from
(
select deptno, avg(salary) as avg_salary, max(avg(salary)) over () as max_avg_salary
from emp
group by deptno
)
where avg_salary = max_avg_salary;
But you want an inline view, another word for a derived table (a subquery in the from clause). That looks way more clumsy. One example without FETCH FIRST and without window functions:
with d as
(
select deptno, avg(salary) as avg_salary
from emp
group by deptno
)
, dmax as
(
select max(avg_salary) as max_avg_salary
from d
)
select d.*
from d
join dmax on dmax.max_avg_salary = d.avg_salary;
I find this very obfuscated and don't recommend it at all. You can do the same without WITH clauses of course. Then it is even less readable.
I don't know why you'd want to write it this way, but if you really want only inline views and no windowing clauses, you can write it this way:
select b.deptno
from (SELECT deptno, avg(sal) avgsal from emp group by deptno ) b
cross join (SELECT max(avgsal) maxavgsal FROM (SELECT avg(sal) avgsal FROM emp group by deptno )) c
where b.avgsal = c.maxavgsal;
This the same thing, if you don't like CROSS JOIN for some reason:
select b.deptno
from (SELECT deptno, avg(sal) avgsal from emp group by deptno ) b
inner join ( SELECT max(avgsal) maxavgsal FROM
( SELECT avg(sal) avgsal FROM emp group by deptno ) ) c
on b.avgsal = c.maxavgsal;

I want to display some columns of a table based on a subquery

I want to Display department number, names and salaries of employees who are earning max salary in their departments which are in the same table.
I am using oracle sql. The Table structure I am using is
Emp(Empno,Ename,Job,Salary,Deptno)
I have read about this and I think that this can be done by the use of correlated sub-queries. The query I fired was
select E1.Ename,E1.Ename,E1.Salary
from Emp E1
where E1.Empno=(
select Empno
from Emp E2
where Salary=(
select max(Salary)
from Emp
where Deptno=E1.Deptno
)
);
This gives an error saying "single-row subquery returns more than one row".
What am I doing wrong? What should do to correct it?
SELECT EmpNo, Ename,Job,Salary,Deptno
FROM
(
SELECT EmpNo, Ename,Job,Salary,Deptno,
DENSE_RANK() OVER (PARTITION BY DeptNo
ORDER BY Salary DESC ) rn
FROM Emp
) a
WHERE a.rn = 1
DENSE_RANK
or by using MAX
SELECT a.*
FROM Emp a
INNER JOIN
(
SELECT DeptNo, MAX(Salary) Max_sal
FROM Emp
GROUP BY DeptNo
) b ON a.DeptNo = b.DeptNo AND
a.Salary = b.Max_SAL