Oracle SQL: ON Clause - sql

SELECT e.employee_id, e.last_name, d.department_id, d.location_id
FROM employees e JOIN departments d
ON (e.department_id=d.department_id);
What exactly does the ON clause do? I'm confused on why e.department_id and d.department_id doesn't cause an error? Doesn't e represent the employees table and d represent the department table? So how can you use e.department_id if department_id is not in the employees table? I'm extremely confused about the purpose of the ON clause.

The ON clause is part of the JOIN syntax and it is used to identify how the tables should be joined to each other, in other words what columns relate the tables to each other.
In your case you are joining the tables on the e.department_id and the d.department_id.
The e is the alias for employees table and the d is the alias for the departments table.
Your current query is performing an INNER JOIN which will return only those rows that are matching in both tables.
If you want to return all employees even if there is no departments associated with an employee row, then you will want to use a LEFT JOIN:
SELECT e.employee_id, e.last_name, d.department_id, d.location_id
FROM employees e
LEFT JOIN departments d
ON e.department_id=d.department_id;

Not sure if it helps you to understand, but it's possible (though not recommended) to re-write this very query with implicit inner join in a form of WHERE clause:
SELECT e.employee_id, e.last_name, d.department_id, d.location_id
FROM employees e, departments d
WHERE (e.department_id=d.department_id);

Related

Right outer JOIN in SQL

I have to list all departments, which do not have any employees assigned to but I don't know how to solve this with a outer-join. The scheme of the database is:
My current statement is
SELECT DISTINCT d.department_name
FROM departments d
RIGHT OUTER JOIN employees e ON NOT e.department_id IS NULL;
but it doesn't work as it should.
My result is this:
It should look like this:
Does anybody know what's wrong?
You can right this as an outer left join like so, correlating on the department_id
SELECT d.department_name
FROM departments d
LEFT JOIN employees e ON e.department_id=d.department_Id
WHERE e.employee_id is null
Or a more intuitive version would be where there does not exist any employees for a department_id
SELECT d.department_name
FROM departments d
WHERE NOT EXISTS (SELECT * FROM employees e WHERE e.department_id=d.department_Id )

SQL in Oracle HR Schema

I have made a query in Oracle HR schema to see the following information:
The city where the department is located
The total number of employees in the department
However, the query cannot be executed correctly and said this is "not a GROUP BY expression".
Does anyone knows what's the problem is? Thanks in advance.
SELECT department_name, city, COUNT(employees.department_id)
FROM departments
JOIN employees on (departments.department_id=employees.department_id)
JOIN locations USING (location_id)
GROUP BY department_name;
You are grouping by department and want to show the department's city. You expect this to work, because each department is in exactly one city. (SQL people call this functional dependency.)
For this to work, ...
there would have to be a unique contraint on the department name or you'd have to group by department_id instead
the DBMS must detect and support functional dependency in aggregation queries
Unfortunately, Oracle doesn't support functional dependency in aggregation queries. It forces us to put every such column in the GROUP BY clause or into an aggregation function.
So either extend the GROUP BY clause:
SELECT d.department_name, l.city, COUNT(e.department_id)
FROM departments d
JOIN employees e ON e.department_id = d.department_id
JOIN locations l USING (location_id)
GROUP BY d.department_name, l.city
ORDER BY d.department_name;
or use some aggregation function as MIN or MAX on that single value.
SELECT d.department_name, MAX(l.city) AS city, COUNT(e.department_id)
FROM departments d
JOIN employees e ON e.department_id = d.department_id
JOIN locations l USING (location_id)
GROUP BY d.department_name
ORDER BY d.department_name;
What I prefer though, is to aggregate first and only then join. You want to join the departments with their employee count, so do just that:
SELECT d.department_name, l.city, COALESCE(e.cnt, 0) AS employee_count
FROM departments d
JOIN locations l USING (location_id)
LEFT JOIN
(
SELECT department_id, COUNT(*) as cnt
FROM employees
GROUP BY department_id
) e ON e.department_id = d.department_id
ORDER BY d.department_name;
The problem is you have both aggregated and non-aggregated column (in your case city in the select list.
As I don't know the structure of location table and considering a department have only one location defined you can use max(city),
SELECT department_name, max(city) city, COUNT(employees.department_id) no_of_employees
FROM departments
JOIN employees on (departments.department_id=employees.department_id)
JOIN locations USING (location_id)
GROUP BY department_name;
As excellently explained by Thorsten, you could also group the data using OVER and PARTITION BY function which would eliminate the use of GROUP BY function.
SELECT d.department_name, l.city, COUNT(e.department_id) OVER (PARTITION BY e.department_id) as emp_count
FROM departments d
JOIN employees e ON e.department_id = d.department_id
JOIN locations l USING (location_id)
ORDER BY d.department_name;

Sql subquery with a group by and a join on two tables

So i have two tables
EMPLOYEE- Contains columns including EMPLOYEE_NAME, DEPARTMENT_ID and SALARY
DEPARTMENTS - Contains columns including DEPARTMENT_NAME, and DEPARTMENT_ID
I need to display the department name and the average slary for each department and order it by the average salaries.
I am new to DBs and am having trouble.
I try to do a subquery in the from field ( this subquery returns exactly what i need minus the department name which requires me to then join the departments table to the results) all the data in the subquery is in one table- employees. while department name is in the departments table.
here is what i tried.
SELECT D.DEPARTMENT_NAME, T.PERDEPT
FROM
(
SELECT DEPARTMENT_ID, AVG(SALARY) AS PERDEPT
FROM EMPLOYEE
GROUP BY DEPARTMENT_ID
ORDER BY PERDEPT
) AS TEST T
JOIN DEPARTMENTS
ON D.DEPARTMENT_ID=T.DEPARTMENT_ID;
This returns a
SQL command not properly terminated
on the line with the AS TEST T
any and all help is greatly appreciated
many thanks
This query should do what you ask:
select d.department_name, avg(e.salary) as avg_salary
from salary_department d
left join employee e on e.department_id = d.department_id
group by d.department_name
order by avg(e.salary)
Simply correct your table aliases as you seem to have two aliases for subquery (TEST and T) and no assignment for D. Adjust SQL with one alias for each table/query reference:
...
(
SELECT ...
) AS T
JOIN DEPARTMENTS D
With that said, you do not even need the subquery as aggregate query with JOIN should suffice, assuming DEPARTMENT_ID is unique in DEPARTMENTS table to not double count the aggregate.
SELECT D.DEPARTMENT_NAME,
AVG(E.SALARY) AS PERDEPT
FROM EMPLOYEE E
JOIN DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID
GROUP BY E.DEPARTMENT_ID,
D.DEPARTMENT_NAME
ORDER BY AVG(SALARY)

Order by subquery

I have the following oracle SQL code, but I can't understand what is the purpose of ordering by a subquery. Anyone can explain it clearly to me ?
SELECT employee_id, last_name
FROM employees e
ORDER BY (
SELECT department_name
FROM departments d
WHERE e.department_id = d.department_id
);
The ordering is done by results from other table. In this case the query returns only results from employees table, but the ordering is done by department_name, which is stored in departments table.
You could achieve identical result by using join, selecting only values from employees table, and ordering by department_name from departments table:
SELECT e.employee_id, e.last_name
FROM employees e INNER JOIN departments d
ON e.department_id = d.department_id
ORDER BY d.department_name
This query is valid if employee must always have a department. If there can be employees without departments then you should use LEFT join instead.
The clear intention of that query is employee_id and last_name from employees should be order by department_name from departments.
Okay, you don't subquery then go for join
select e.employee_id,e.last_name from employees e join departments d on
e.department_id = d.department_id order by d.department_name;

Join query in oracle 10g

I have written this query to display the last name, department number, and department name from all employees who work in toronto.
select last_name, job_id, department_id, department_name
from employees e
join departments d on d.department_id=e.department_id
join locations l on d.location_id=l.location_id and l.city='Toronto';
I am getting this error
ORA-00918: column ambiguously defined
When a column exists on both tables participating in a join you need to prefix the column name with an alias to specify which column you would like. In your join the department_id is shared by both tables, you can specify which column you would like using d.department_id in the selected columns list.
select
last_name,
job_id,
d.department_id, --specify which table you want this ambiguous column from
department_name
from employees e
join departments d
on d.department_id=e.department_id
join locations l on
d.location_id=l.location_id and l.city='Toronto';
use alias name to select the column from any particular table. For example, simply writing department_id will raise error, since it is available on multiple tables and it raise ambiguity error
So, better solution is select column with their alias name like
select e.last_name, e.job_id, e.department_id, d.department_name
from employees e
join departments d on d.department_id=e.department_id
join locations l on d.location_id=l.location_id and l.city='Toronto';
change the first line to:
select e.last_name, e.job_id, e.department_id, d.department_name
Try this:
select last_name, job_id, department_id, department_name
from employees
join departments using(department_id)
join locations using(location_id) where city='Toronto';