Select from two tables without using an OR - sql

I have two tables: employee and department
employee
name | salary | dNumber
A | 20000 | 1
B | 25000 | 1
C | 10000 | 5
D | 10000 | 1
department
departmentName | departmentNumber
math | 1
science | 2
How can I select from these two tables without using boolean operators like OR & AND?
I want to find the employee that works for department 1 or has a salary greater than $20,000.
What I tried:
select name from employee
where salary>= 20000 from (where dNumber in (select departmentNumber from department
where departmentNumber = 5));
That doesn't work because:
ERROR: syntax error at or near "where"

If the requirement is to not use an OR, you could use UNION instead. Since you filter the department on its number, not on its name, you do not need the second table at all:
SELECT name FROM employee WHERE salary > 20000
UNION
SELECT name FROM employee WHERE dNumber = 1
If you wanted to filter the department by name, a join or a subquery would be required:
SELECT name FROM employee WHERE salary > 20000
UNION
SELECT name FROM employee e
JOIN department d ON e.dNumber=d.departmentNumber
WHERE departmentName = 'math'

Here's how you do it:
select name from employee where where dnumber = 1
union
select name from employee where salary > 20000
Basically, union works like the OR operator and intersection works like the AND operator.

Related

Find the average value from the previous row and the current row for a same department id

I have 3 employees with the same departments. i wanted to find the average of their salaries based on the previous rows.
For example
Employee_id department_id salary avg(salary)
101 1 5000 5000
102 1 10000 7500
103 1 15000 10000
This is like for department id as 1 with first salary we find the average as 5000 for employee id 101 and for the same department id as 1 for the employee id as 102 , we find the average for the 2 values grouped by department id.
Hence
average is (10000+5000) /2 = 7500
But for the employee id as 103, department id is 1 and is grouped with all the above three values of amount.
Hence,
average of salary is (10000+5000+15000)/3 = 10000
The requirement is i have been asked to use query_partition_clause and order_by_clause.
Hence i tried as follows,
select avg(salary) OVER (partition by department_id ORDER BY department_id ) salary, department_id, salary from employee
But i am always getting the values by considering the department of 3 data values.
Henceforth can somebody help on this resolution?
Many Thanks for the help.
Use ORDER BY salary (or ORDER BY employee_id) rather than ORDER BY department_id:
Oracle Setup:
CREATE TABLE employees ( Employee_id, department_id, salary ) AS
SELECT 101, 1, 5000 FROM DUAL UNION ALL
SELECT 102, 1, 10000 FROM DUAL UNION ALL
SELECT 103, 1, 15000 FROM DUAL;
Query:
SELECT e.*,
AVG( salary ) OVER ( PARTITION BY department_id ORDER BY salary ) AS avg_salary
FROM employees e
Output:
EMPLOYEE_ID | DEPARTMENT_ID | SALARY | AVG_SALARY
----------: | ------------: | -----: | ---------:
101 | 1 | 5000 | 5000
102 | 1 | 10000 | 7500
103 | 1 | 15000 | 10000
db<>fiddle here
SELECT EMPLOYEE_ID,
DEPARTMENT_ID,
SALARY,
(SELECT AVG(SALARY)
FROM EMPLOYEES B
WHERE B.EMPLOYEE_ID <= A.EMPLOYEE_ID) AVG_SALARY
FROM EMPLOYEES A
GROUP BY EMPLOYEE_ID,
DEPARTMENT_ID,
SALARY
A sub query can be done in the query itself by filtering the employee ID. I hope I have helped with something.

Removing duplicates by adding them up [SQL]

I have a query like this:
select employee_id, salary
from salary
left join employee on salary.employee_id=employee.id_employee;
It returns me these results
EMPLOYEE ID | SALARY
-------------|-------
1 | 50
2 | 50
3 | 50
1 | 30
How do I remove duplicates by adding them up, like this:
EMPLOYEE ID | SALARY
------------|--------
1 | 80
2 | 50
3 | 50
There is no reason for a left join from salary to employee. Presumably, every employee_id in salary refers to a valid employee. So, this should do what you want:
select s.employee_id, sum(s.salary) as salary
from salary s
group by s.employee_id;
If you want all employees, even those who are not in the salary table, then an outer join is appropriate -- but employee should be first:
select e.id_employee, sum(s.salary) as salary
from employee e left join
salary s
on s.employee_id = e.id_employee
group by e.id_employee;
Employees not in salary will have a value of NULL.
Note that the group by condition in this query is on employee, the first table in the left join.
select employee_id, SUM(salary) as salary
from salary
left join employee on salary.employee_id=employee.id_employee
group by emplyee_id;
This is exactly what a group by clause is for. You'll have to group by the emplopyee_id and specify how you want to aggregate the salary:
SELECT employee_id, SUM(salary)
FROM salary
GROUP BY employee_id

Select rows where every child row meets a condition

In my Oracle DB, I have two tables in a one-to-many relationship: Managers and Employees.
+------------+-------+------------+
| Manager_ID | Name | Department |
+------------+-------+------------+
| 1 | Steve | Sales |
| 2 | Ben | Sales |
| 3 | Molly | Accounts |
+------------+-------+------------+
+-------------+------------+--------+-----+
| Employee_ID | Manager_ID | Name | Age |
+-------------+------------+--------+-----+
| 1 | 1 | Kyle | 25 |
| 2 | 1 | Gary | 31 |
| 3 | 2 | Renee | 31 |
| 4 | 2 | Oliver | 32 |
+-------------+------------+--------+-----+
How do I select only those Managers where every one of his Employees is over the age of 30?
In my example data, the only Manager who meets this condition is Ben, because both of his employees are over 30.
I thought something like this would do it, but it's wrong:
SELECT m.manager_id
FROM managers m
WHERE m.manager_id IN (SELECT e.manager_id
FROM employees e
GROUP BY e.manager_id
HAVING e.age > 30)
Use not exists :
select m.*
from manager m
where not exists (select 1
from Employees e
where e.Manager_ID = m.Manager_ID and e.Age < 30
) and
exists (select 1 from Employees e where e.Manager_ID = m.Manager_ID)
The only thing I don't like about Yogesh's answer (which I upvoted, since it's probably the way I'd write it) is that you have to go to the employees table a second time, to make sure the manager actually has at least one employee.
On the plus side, the NOT EXISTS that Yogesh used will allow Oracle to stop looking at a manager's employees once it finds one that is too young. So, maybe it's a toss-up.
I'll offer this alternative. It is shorter than the NOT EXISTS and does not have to go to the employees table a second time.
SELECT m.*
FROM manager m
CROSS APPLY (
SELECT min(age) min_age
FROM employee e
WHERE e.manager_id = m.manager_id ) ma
where ma.min_age >= 30;
Using sub-query for counts
SQL> WITH manager(Manager_ID, Name, Department) AS (
2 SELECT 1, 'Steve', 'Sales' FROM dual UNION ALL
3 SELECT 2, 'Ben', 'Sales' FROM dual UNION ALL
4 SELECT 3, 'Molly', 'Accounts' FROM dual),
5 employee(Employee_ID, Manager_ID, Name, Age) AS (
6 SELECT 1 , 1, 'Kyle', 25 FROM dual UNION ALL
7 SELECT 2 ,1, 'Gary', 31 FROM dual UNION ALL
8 SELECT 3, 2, 'Renee', 31 FROM dual UNION ALL
9 SELECT 4, 2 , 'Oliver', 32 FROM dual)
10 ---------------------------
11 --- End of data preparation
12 ---------------------------
13 SELECT m.name
14 FROM manager m
15 JOIN (SELECT manager_id,
16 COUNT(1) total,
17 COUNT(CASE WHEN age > 30 THEN 1 ELSE NULL END) age_30_above
18 FROM employee
19 GROUP BY manager_id) ee
20 ON m.manager_id = ee.manager_id
21 WHERE total = age_30_above;
Output
NAME
-----
Ben
Your query will be:
SELECT m.name
FROM manager m
JOIN (SELECT manager_id,
COUNT(1) total,
COUNT(CASE WHEN age > 30 THEN 1 ELSE NULL END) age_30_above
FROM employee
GROUP BY manager_id) ee
ON m.manager_id = ee.manager_id
WHERE total = age_30_above;
SELECT manager_id
FROM employees -- managers
minus
select manager_id
from employees
where age <= 30
You can use ALL function like this:
SELECT m.manager_id
FROM managers m
WHERE (30 <= ALL (SELECT e.age FROM employees e WHERE e.manager_id = m.manager_id));
You might want to reverse the conditions, select all managers, who dont have any employee below 30
select * from managers
where manager_id not in (select manager_id
from employees
where age < 30)

SQL - Retrieving data within groups before and after some condition

With the two following tables:
EMPLOYEE (Fname, Lname, SSN, DNO)
DEPARTMENT (Dname, Dnumber)
For each department that has more than five employees, retrieve the
department name and the number of its employees who are making more
than $40,000
Here is an incorrect solution to this:
SELECT
dname,
COUNT(*)
FROM
Department, Employee
WHERE
dnumber = dno
AND salary > 40000
GROUP BY
dname
HAVING
COUNT(*) > 5;
It is clear that it would not list any department that have five or more employees unless they all have more than $40,000 salary, because where is applied before group by clause. which is not what we want.
Here is the correct solution:
SELECT
dname, COUNT(*)
FROM
Department, Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (SELECT dno
FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5)
GROUP BY
dname
I cant see why is this correct?
Isn't it going to restrict the rows first with employees who have more than $40,000, then do the grouping just like the first query? what is different here?
Sub-Query, the basic:
First, let make this query a bit easier to read :
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (
SELECT dno
FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5
)
GROUP BY dname
As you can see, there is what we call a "sub-query": a query inside the query.
This is the part in dno IN (/*HERE is the Sub-query*/).
As in mathematics parenthesis are run first, so SQL will go find DNO that have more than 5 employees, producing the following query :
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND dno IN (
'dno10emp', 'dno24emp', 'dno45emp'
)
GROUP BY dname
Now, you find yourself with a simple query that will produce the result:
of department that have a least one employee with >40k$ salary
and are part of the department with more the 5 employee
What's wrong ?!
Well, I'll said your "good query" isn't that good, and that's why you're struggling: It'll not bring department if they don't have at least one employee with > 40k$.
Here is the query that'll do this :
SELECT
Department.dname,
COUNT(Employee.salary)
FROM
Department
LEFT JOIN Employee
ON Department.dnumber = Employee.dno
AND Employee.salary > 40000
WHERE
Department.dnumber IN (
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
)
GROUP BY Department.dname
This will bring you all department that have at least 6 employee, then count the number of employee with at least 40K$ (a department could have 0).
Could you show me ?
As an image worth a thousand word :
SQL Fiddle
MySQL 5.6 Schema Setup:
| dname | nb | salary |
|-------------------|----|--------|
| accounting | 2 | 30000 |
| accounting | 4 | 50000 |
| boss | 6 | 150000 |
| garbage-collector | 6 | 15000 |
Query 1:
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
GROUP BY dname
HAVING COUNT(*) > 5
Results:
| dname | COUNT(*) |
|-------|----------|
| boss | 6 |
Query 2:
SELECT
dname,
COUNT(*)
FROM
Department,
Employee
WHERE
dnumber = dno
AND salary > 40000
AND
dno IN (
SELECT dno FROM Employee
GROUP BY dno
HAVING COUNT(*) > 5
)
GROUP BY dname
Results:
| dname | COUNT(*) |
|------------|----------|
| accounting | 4 |
| boss | 6 |
Query 3:
SELECT
Department.dname,
COUNT(Employee.salary)
FROM
Department
LEFT JOIN Employee
ON Department.dnumber = Employee.dno
AND Employee.salary > 40000
WHERE
Department.dnumber IN (
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
)
GROUP BY Department.dname
Results:
| dname | COUNT(Employee.salary) |
|-------------------|------------------------|
| accounting | 4 |
| boss | 6 |
| garbage-collector | 0 |
See sample data below.
http://sqlfiddle.com/#!9/357d29/2
The first query will only get departments with 6 or more highy paid employees WHILE the 2nd query will get highly paid employees of those departments with 6 or more employees. Below sample will not show in the 1st query but will show in the 2nd query.
Department Employee Salary
accounting john doe 50k
jan smith 55k
dan brown 60k
eric murphy 60k
al daniels 70k
ellen boyle 30k
1st query: nothing because only five emp have > 40k salary
2nd query: All except ellen boyle. Department has > 5 employees and all except 1 has > 40k salary
For the record, you already got correct answers. I'll just try to explain it in a different way.
Your first query has 1 select statement. It only returns employees with salary > 40k and from departments > 5 employees. Every record will only contain information about an employee with salary > 40k and from departments > 5 employees.
Your second query has 2 select statements:
This is the first one:
Select dname, count(*)
from Department, Employee
where dnumber = dno
and salary > 40000
it returns the count of all employees, by department name who earn > 40000. There are no conditions on the count(*) here. And the condition on the salary has no power over the second select statement:
SELECT Employee.dno
FROM Employee
GROUP BY Employee.dno
HAVING COUNT(*) > 5
This one returns ALL employees in all departments. This is where we have the condition on the count(*) - but it is only applied locally, to limit the number of employees per department.
And then two statements are joined together - so, first we limit the departments to the ones we are interested in, and then from those only select high-salary employees.
First, never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
I think the best and simplest solution uses conditional aggregation:
SELECT d.dname, SUM(CASE WHEN e.salary > 40000 THEN 1 ELSE 0 END) as num_40kplus
FROM Department d JOIN
Employee e
ON d.dno = e.dnumber
GROUP BY dname
HAVING COUNT(*) > 5;
I see no reason why a subquery would be necessary or desirable.

How to Improve This Self-Joins

I am learning Oracle SQL by working with its primitive HR schema where there is EMPLOYEES table which has three columns that I'm mainly interested in: MANAGER_ID, which is basically a self reference to EMPLOYEES.EMPLOYEE_ID, DEPARTMENT_ID, and SALARY. (You can find the schema diagram and schema objects here).
I wish, for each employee, to retrieve his/her SALARY, alongside of employee's manager's departmental average salary. For instance, if we have the following (EMPLOYEE_ID = 140 is the interested party here):
+-------------+--------+---------------+------------+
| EMPLOYEE_ID | SALARY | DEPARTMENT_ID | MANAGER_ID |
+-------------+--------+---------------+------------+
| 140 | 12000 | 50 | 110 |
| 110 | 20000 | 60 | 101 |
| 156 | 18000 | 60 | 101 |
| 175 | 15000 | 60 | 105 |
| 320 | 24000 | 60 | 105 |
+-------------+--------+---------------+------------+
I am interested in obtaining an average salary of all the managers (not all other non-managerial employees) in department where employee's manager works at (in this case, DEPARTMENT_ID =60), and compare it with employee's (in this case, 140). In a sample data above, the output should be:
+-------------+--------+-------------+-------------+------------+
| EMPLOYEE_ID | SALARY | AVG_MGR_SAL | MGR_DEPT_ID | MANAGER_ID |
+-------------+--------+-------------+-------------+------------+
| 140 | 12000 | 19250 | 60 | 110 |
+-------------+--------+-------------+-------------+------------+
where we have four (4) managers working in department 60, and $19250 being calculated as (20000 + 18000 + 15000 + 24000) / 4. I have come up with the following query that seems to work (and excludes those employees that don't have a manager):
select
employee_id
, salary employee_salary
, trunc(mgr_info.avg_manager_salary_per_dept, 0) emp_manager_avg_sal_dept
, mgr_info.manager_dept_id
, mgr_info.manager_id
from employees
join (
select
e1.employee_id manager_id
, e1.department_id manager_dept_id
, e1.salary manager_salary
, avg(e1.salary) over (partition by e1.department_id) avg_manager_salary_per_dept
from employees e1
join (
select distinct manager_id
from employees
where manager_id is not null
) mgr_ids
on e1.employee_id = mgr_ids.manager_id
) mgr_info
on employees.manager_id = mgr_info.manager_id
order by employee_id
However, I feel like that there should be a better way of getting the same result with fewer self-joins. Is there a way to get a better performance?
Something like this... You only need one join, you can compute the average salary for the manager's department on the "manager" copy of the table. I only included a few columns, you may need more, or fewer, but I believe the core of what you wanted is covered.
(NOTE: Edited since I realized I missed one detail in the requirement)
select e.employee_id as employee_id,
e.salary as employee_salary,
m.employee_id as manager_id,
m.department_id as manager_dept_id,
m.avg_salary as avg_sal_of_mgr_dept
from hr.employees e inner join
( select employee_id, department_id,
avg(salary) over (partition by department_id) as avg_salary
from hr.employees
where employee_id in (select manager_id from hr.employees)
) m
on e.manager_id = m.employee_id
;
Here is an option which uses a series of joins to get your result:
SELECT DISTINCT t1.EMPLOYEE_ID,
t1.SALARY,
t1.DEPARTMENT_ID,
COALESCE(t2.SALARY, 0.0) AS ManagerAvgSal
FROM employees t1
LEFT JOIN
(
SELECT e1.DEPARTMENT_ID, AVG(e1.SALARY) AS SALARY
FROM employees e1
WHERE e1.EMPLOYEE_ID IN (SELECT DISTINCT MANAGER_ID FROM employees)
GROUP BY e1.DEPARTMENT_ID
) t2
ON t1.DEPARTMENT_ID = t2.DEPARTMENT_ID