I'm totally confused about using aggregate functions after where clause or anywhere after mentioning the table_name
EMP Table as posted on http://viditkothari.co.in/post/27045365558/sql-commands-1
Query Info:
Display all the emp who have sal equal to any of the emp of deptno 30
Suggested query:
select *
from employee_4521
where sal having (select sal
from employee_4521
where deptno = 30);
Returns following error:
ERROR at line 1:
ORA-00920: invalid relational operator
with an asterik marked under 'h' of having clause
There doesn't appear to be any reason to use an aggregate function here. Just use an IN or an EXISTS
select *
from employee_4521
where sal in (select sal
from employee_4521
where deptno=30);
or
select *
from employee_4521 a
where exists( select 1
from employee_4521 b
where b.deptno = 30
and a.sal = b.sal );
Related
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 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
I have a table emp (ename,eid,did,sal,sex) where column did is foreign key with another table dept (did,dname).
I want to get the max sal of the company along with name and did of the person who is getting it.
I am executing following query
select did,ename ,max(sal) from emp;
But the Result is :
ORA-00937: not a single-group group function
so My question is can't I have more than 1 columns ?
You can't use MAX aggregate function like this. Using MAX without a GROUP BY clause will just return one record with the maximum sal value. You are not allowed to place any more non-aggregated fields in the SELECT clause.
If you want to get the record having the maximum sal value, then you have to do a self-join:
select e1.*
from emp as e1
inner join (
select max(sal) as max_sal
from emp
) as e2 on e1.sal = e2.max_sal
Note: The above query will return more than one records from table emp, in case more than one records share the same maximum sal value.
Edit:
If you want to get the maximum salary per department then you have to include a GROUP BY clause in the derived table used:
select e1.*
from emp as e1
inner join (
select did, max(sal) as max_sal
from emp
group by did
) as e2 on e1.did = e2.did and e1.sal = e2.max_sal
You would have to group the results by the sal column:
select did,ename,max(sal) from emp group by sal
MAX operates over the group specified in a GROUP BY clause and finds the maximum value for each group. When not specifying a set of columns to group by, it finds the max for the entire results set.
You need to find the max sal, then you can query the rows that match:
select did, ename, sal
from emp
where sal = (select max(sal) from emp)
I'm learning SQL using Oracle 10g. I need a query that returns the department with the most employees to use it in a update sentence. I already solved it, but I couldn't figure out why this query won't work:
select deptno
from (select deptno,
count(*) num
from emp
group by deptno)
where not num < any(select count(deptno)
from emp
group by deptno)
It puzzles me more since according to the documentation it should be equivalent and optimized into the following:
select deptno
from (select deptno,
count(*) num
from emp
group by deptno )
where not exists( select deptno,
count(*)
from emp
having count(*) > num
group by deptno)
That one works without errors. The following also work:
select deptno
from (select deptno,
count(*) num
from emp
group by deptno)
where num = (select max(alias)
from (select count(deptno) alias
from emp
group by deptno))
select deptno
from emp
group by deptno
having not count(deptno) < any( select count(deptno)
from emp
group by deptno)
Edit. Probably it'll help if I post the return values of the inner selects.
The first select returns:
Dept. Number Employees
30 6
20 5
10 3
The last one returns (3,5,6)
I checked them individually. It's also weird that if I put the values manually it works as expected and will return 30 as the department with most employees.
select deptno
from (select deptno,
count(*) num
from emp
group by deptno)
where not num < any(6,5,3)
I'm using Oracle 10g 10.2.0.1.0
Last edit, probably. Still don't know what's happening, but the behaviour is as if the last select is returning null somehow. So, even if I remove the ´not´, it still doesn't select anything.
If someone is interested I also found this useful:
TSQL - SOME | ANY why are they same with different names?
Read the first answer. It's probably better to avoid the use of any/some, all.
Here's a similar example which may clarify things (Standard SQL, can be easily transformed for Oracle):
WITH T
AS
(
SELECT *
FROM (
VALUES (0),
(1),
(2),
(NULL)
) AS T (c)
)
SELECT DISTINCT c
FROM T
WHERE 1 > ALL (SELECT c FROM T T2);
This returns the empty set, which is reasonable: given the presence of the null in the table, 1 > NULL is UNKNOWN, therefore it is not known whether the value 1 is greater than all values in the set.
However, adding the NOT operator:
WHERE NOT 1 > ALL (SELECT c FROM T T2);
returns all values in the set, including the null value. At first glance this seems wrong: given that 1 > 2 is FALSE we can say with certainty that the value 1 is not greater than all values in the set, regardless of the null.
However, in this case the NOT is simply flipping the earlier result i.e. the opposite of all no rows is all rows! ;)
Further consider the negated comparison using a column (rather than the literal value 1):
WHERE NOT c > ALL (SELECT c FROM T T2);
This time it returns all rows except for the null value.
Correction (update)
not num < any(select ...)
should be the same as your other queries. You can also try this variation:
num >= ALL(select ...)
but I can't understand why yours is giving wrong results. Perhaps because of the not precedence. Can you trythis instead?:
not ( num < ANY(select ...) )
Full queries:
select deptno
from (select deptno, count(*) num from emp group by deptno)
where num >= all(select count(deptno) from emp group by deptno)
and:
select deptno
from (select deptno, count(*) num from emp group by deptno)
where not ( num < any(select count(deptno) from emp group by deptno) )
Consider the Oracle emp table. I'd like to get the employees with the top salary with department = 20 and job = clerk. Also assume that there is no "empno" column, and that the primary key involves a number of columns. You can do this with:
select * from scott.emp
where deptno = 20 and job = 'CLERK'
and sal = (select max(sal) from scott.emp
where deptno = 20 and job = 'CLERK')
This works, but I have to duplicate the test deptno = 20 and job = 'CLERK', which I would like to avoid. Is there a more elegant way to write this, maybe using a group by? BTW, if this matters, I am using Oracle.
The following is slightly over-engineered, but is a good SQL pattern for "top x" queries.
SELECT
*
FROM
scott.emp
WHERE
(deptno,job,sal) IN
(SELECT
deptno,
job,
max(sal)
FROM
scott.emp
WHERE
deptno = 20
and job = 'CLERK'
GROUP BY
deptno,
job
)
Also note that this will work in Oracle and Postgress (i think) but not MS SQL. For something similar in MS SQL see question SQL Query to get latest price
If I was certain of the targeted database I'd go with Mark Nold's solution, but if you ever want some dialect agnostic SQL*, try
SELECT *
FROM scott.emp e
WHERE e.deptno = 20
AND e.job = 'CLERK'
AND e.sal = (
SELECT MAX(e2.sal)
FROM scott.emp e2
WHERE e.deptno = e2.deptno
AND e.job = e2.job
)
*I believe this should work everywhere, but I don't have the environments to test it.
In Oracle I'd do it with an analytical function, so you'd only query the emp table once :
SELECT *
FROM (SELECT e.*, MAX (sal) OVER () AS max_sal
FROM scott.emp e
WHERE deptno = 20
AND job = 'CLERK')
WHERE sal = max_sal
It's simpler, easier to read and more efficient.
If you want to modify it to list list this information for all departments, then you'll need to use the "PARTITION BY" clause in OVER:
SELECT *
FROM (SELECT e.*, MAX (sal) OVER (PARTITION BY deptno) AS max_sal
FROM scott.emp e
WHERE job = 'CLERK')
WHERE sal = max_sal
ORDER BY deptno
That's great! I didn't know you could do a comparison of (x, y, z) with the result of a SELECT statement. This works great with Oracle.
As a side-note for other readers, the above query is missing a "=" after "(deptno,job,sal)". Maybe the Stack Overflow formatter ate it (?).
Again, thanks Mark.
In Oracle you can also use the EXISTS statement, which in some cases is faster.
For example...
SELECT name, number
FROM cust
WHERE cust IN
( SELECT cust_id FROM big_table )
AND entered > SYSDATE -1
would be slow.
but
SELECT name, number
FROM cust c
WHERE EXISTS
( SELECT cust_id FROM big_table WHERE cust_id=c.cust_id )
AND entered > SYSDATE -1
would be very fast with proper indexing. You can also use this with multiple parameters.
There are many solutions. You could also keep your original query layout by simply adding table aliases and joining on the column names, you would still only have DEPTNO = 20 and JOB = 'CLERK' in the query once.
SELECT
*
FROM
scott.emp emptbl
WHERE
emptbl.DEPTNO = 20
AND emptbl.JOB = 'CLERK'
AND emptbl.SAL =
(
select
max(salmax.SAL)
from
scott.emp salmax
where
salmax.DEPTNO = emptbl.DEPTNO
AND salmax.JOB = emptbl.JOB
)
It could also be noted that the key word "ALL" can be used for these types of queries which would allow you to remove the "MAX" function.
SELECT
*
FROM
scott.emp emptbl
WHERE
emptbl.DEPTNO = 20
AND emptbl.JOB = 'CLERK'
AND emptbl.SAL >= ALL
(
select
salmax.SAL
from
scott.emp salmax
where
salmax.DEPTNO = emptbl.DEPTNO
AND salmax.JOB = emptbl.JOB
)
I hope that helps and makes sense.