What are the equivalent joins written in the Oracle's old join syntax of these queries?
SELECT first_name, last_name, department_name, job_title
FROM employees e RIGHT JOIN departments d
ON(e.department_id = d.department_id)
RIGHT JOIN jobs j USING(job_id);
-->106 rows returned
SELECT first_name, last_name, department_name, job_title
FROM employees e RIGHT JOIN jobs j
ON(e.job_id = j.job_id)
RIGHT JOIN departments d
USING(department_id);
--> 122 rows returned
I would do something like this (for the first query) - making explicit the fact that a multiple join is, by definition, an iteration of joins of two tables (or more generally "rowsets") at a time. Think of it as "using parentheses explicitly".
select first_name, last_name, department_name, job_title
from (
select first_name, last_name, job_id, department_name
from employees e, departments d
where e.department_id (+) = d.department_id
) sq
, jobs j
where sq.job_id (+) = j.job_id
;
This can be rewritten (perhaps) using a single SELECT statement, with more WHERE conditions - but the query will be less readable; it wont' be quite as clear what it is doing.
Respectively:
SELECT first_name,
last_name,
department_name,
job_title
FROM employees e,
jobs j,
departments d
WHERE e.job_id (+) = j.job_id
AND e.department_id = d.department_id (+);
and:
SELECT first_name,
last_name,
department_name,
job_title
FROM employees e,
departments d,
jobs j
WHERE e.department_id (+) = d.department_id
AND e.job_id = j.job_id (+);
db<>fiddle here
However, please just use the ANSI join syntax. The old legacy join syntax is confusing to read and you will get errors from putting the (+) on the wrong side of the join condition and you should be teaching people how to use the less-confusing, "new" (its hard to call it new when its been around since Oracle 9i in 2001) syntax rather than reverting to old methods.
Just to add to Mathguy's answer, this is interesting because those innocent-looking right joins are not what they seem. My first (incorrect) attempt was this:
select e.department_id, e.job_id, e.first_name, e.last_name, d.department_name
from jobs j
, departments d
, employees e
where e.job_id(+) = j.job_id
and e.department_id(+) = d.department_id;
but as Mathguy points out it gives different results because of the departments with no employees and the cross join between departments and jobs, and a subtle join precedence effect that appears as a result of the right joins not being in one chain.
I'm not sure what the intention of the original query is. Using the Oracle HR demo schema, the results are the same as an inner join, but only because every job has at least one employee. This illustrates a pitfall in testing outer join queries, as you might run a test, get the same results, and think your rewrite was logically the same thing when it is not.
If you rewrite the original right joins as left joins, it would have to become something like this:
select e.department_id, e.job_id, e.first_name, e.last_name, d.department_name
from jobs j
left join (
departments d
left join employees e on e.department_id = d.department_id
)
on e.job_id = j.job_id;
(You could also expand the departments > employees join into an inline view or with clause, or use an outer apply construction to include the job_id join.)
This is because the two right joins in the original query are driven from jobs and departments, so even though the outer join from departments to employees includes the 16 departments with no employees, once we outer join from jobs to that, we implicitly exclude rows with no job_id, because we are driving it from jobs. So the outer join to departments is filtered to become in effect an inner join, and so long as all jobs have corresponding employees then that gives the same results as an inner join too. To see the difference you would have to insert another job, which adds a row in the results with the job title but no employee details.
Therefore the old-style version needs to be either this:
select de.first_name, de.last_name, de.department_name, j.job_title
from jobs j
, lateral (
select e.department_id, e.job_id, e.first_name, e.last_name, d.department_name
from departments d
, employees e
where e.department_id(+) = d.department_id
) de
where de.job_id(+) = j.job_id;
or without lateral:
select first_name, last_name, department_name, job_title
from jobs j
, ( select e.first_name, e.last_name, e.job_id, d.department_name
from departments d, employees e
where e.department_id (+) = d.department_id ) de
where de.job_id(+) = j.job_id
The second query just switches jobs and departments:
select first_name, last_name, department_name, job_title
from departments d
, ( select e.first_name, e.last_name, e.department_id, e.job_id, j.job_title
from jobs j, employees e
where e.job_id(+) = j.job_id ) je
where je.department_id(+) = d.department_id
Related
create table EmployeesUK_9035
2 as
3 select e.employee_id,e.first_name || e.Last_name "Name",e.Salary,l.City,e.hire_date from employees e join locations l
4 where e.employee_id in(select e.employee_id from employees e join departments d on(e.department_id=d.department_id) join locations l on(d.location_id=l.location_id) where l.city='London');
...from employees e join locations l
4 where
You have missed out the ON section of the JOIN clause (in the main query, not the subquery).
This is the sort of bloomer which should be easy to spot. But because your code is all bunched up it's hard to diagnose. Laying code out nicely isn't just some neat-freakery on the part of experienced developers: readability is actually a feature of the code. Like this ...
create table EmployeesUK_9035
as
select e.employee_id,
e.first_name || e.Last_name "Name",
e.Salary,
l.City,
e.hire_date
from employees e
join locations l
where e.employee_id in (select e.employee_id
from employees e
join departments d
on (e.department_id=d.department_id)
join locations l
on (d.location_id=l.location_id)
where l.city = 'London')
;
See how easy it is to spot the missing line? You need an ON clause to join EMPLOYEES and LOCATIONS. However,given the join of the subquery you probably also need to include DEPARTMENTS in the main query because there appears to be no join between the two tables. In which case the query might simplify to
create table EmployeesUK_9035
as
select e.employee_id,
e.first_name || e.Last_name "Name",
e.Salary,
l.City,
e.hire_date
from employees e
join departments d
on (e.department_id=d.department_id)
join locations l
on (d.location_id=l.location_id)
where l.city = 'London'
;
Incidentally please don't use double-quotes and mixed-case for column-aliases when creating a table. You will have to use "Name" in double-quotes and the exact same case every time you reference it, which is a pain because Oracle code is generally case insensitive; that is, all Oracle identifiers are in upper-case by default but case doesn't matter provided we don't wrap the identifiers in double-quotes.
JOIN should have the ON (to show what you're joining those tables on), while yours doesn't.
I set ON 1 = 1, but you should use columns from EMPLOYEES and LOCATIONS tables.
CREATE TABLE EmployeesUK_9035
AS
SELECT e.employee_id,
e.first_name || e.Last_name "Name",
e.Salary,
l.City,
e.hire_date
FROM employees e JOIN locations l
ON 1 = 1 --> this
WHERE e.employee_id IN (SELECT e.employee_id
FROM employees e
JOIN departments d
ON (e.department_id = d.department_id)
JOIN locations l
ON (d.location_id = l.location_id)
WHERE l.city = 'London');
I'm trying to display the last name of the lowest paid employees from each city. The city column falls under a table titled LOCATIONS while employee information(salary, last name) falls under EMPLOYEES. Both of these tables are related share no common table, so I have to rely on a third table, DEPARTMENTS to connect the two as DEPARTMENTS contains a department_id that it shares with EMPLOYEES as well as a LOCATION_ID that it shares with LOCATIONS. This is what I have so far, but I'm having trouble with this as I've mostly worked with only two tables in the past.
SELECT LAST_NAME
FROM EMPLOYEES
WHERE (DEPARTMENT_ID) IN
(SELECT DEPARTMENT_ID
FROM DEPARTMENTS
WHERE LOCATION_ID IN
(SELECT LOCATION_ID
FROM LOCATIONS
GROUP BY CITY
HAVING MIN(SALARY)));
This seems to be an assignment in an intro course in SQL. So let's assume you can't use analytic functions, match_recognize clause, etc. Just joins and aggregates.
In the subquery in the WHERE clause below, we compute the min salary for each city. We need to join all three tables for this. Then in the overall query we join the three tables again, and we use the subquery for an IN condition (a semi-join). The overall query looks like this:
select e.last_name
from employees e join departments d
on e.department_id = d.department_id
join locations l
on d.location_id = l.location_id
where ( e.salary, l.city ) in
(
select min(salary), city
from employees e join departments d
on e.department_id = d.department_id
join locations l
on d.location_id = l.location_id
group by city
)
;
You should separate out the concept of table joins from WHERE clauses.
Use WHERE for filtering data, use JOIN for connecting data together.
I think this is what you are wanting. By the way, lose the ALL CAPS if you can.
SELECT
LAST_NAME
FROM
EMPLOYEES
INNER JOIN (
SELECT
DEPARTMENTS.DEPARTMENT_ID,
CITY,
MIN(SALARY) AS LOWEST_SALARY
FROM
EMPLOYEES
INNER JOIN DEPARTMENTS ON EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID
INNER JOIN LOCATIONS ON DEPARTMENTS.LOCATION_ID = LOCATIONS.LOCATION_ID
GROUP BY
DEPARTMENTS.DEPARTMENT_ID,
LOCATIONS.CITY
) AS MINIMUM_SALARIES
ON EMPLOYEES.DEPARTMENT_ID = MINIMUM_SALARIES.DEPARTMENT_ID
AND EMPLOYEES.SALARY = MINIMUM_SALARIES.LOWEST_SALARY
First of all join the tables, so you see city and employee in one row. If we group by city we get the minimum salary per city.
with city_employees as
(
select l.city, e.*
from locations l
join departments d using (location_id)
join employees e using (department_id)
)
select last_name
from city_employees
where (city, salary) in
(
select city, min(salary)
from city_employees
group by l.city
);
It is easier to achieve the same, however, with window functions (min over or rank over here).
select last_name
from
(
select
e.last_name,
e.salary,
min(e.salary) over (partition by l.city) as min_salary
from locations l
join departments d using (location_id)
join employees e using (department_id)
)
where salary = min_salary;
I'm new too SQL and I've been struggling to write this query. I want to find the SUM of all salaries for employees in a give department, let's say 'M', and a given hire date, let's say '2002', any ideas? I'm thinking I have to JOIN the tables somehow but having trouble, I've set up a schema like this.
jobs table and columns
JOBS
------------
job_id
salary
hire_date
employees table and columns
EMPLOYEES
------------
employee_id
name
job_id
department_id
department table and columns
DEPARTMENTS
------------
department_id
department_name
This is very similar to the way the HR schema does it in Oracle so I think the schema should be OK just need help with the query now.
You need a statement like this:
SELECT e.name,
d.department_name,
SUM(j.salary)
FROM employees e,
departments d,
jobs j
WHERE d.department_name = 'M'
AND TO_CHAR(j.hire_date, 'YYYY') = '2002'
AND d.department_id = e.department_id
AND e.job_id = j.job_id
GROUP BY e.name,
d.department_name;
FWIW, you shouldn't use the old ANSI-89 implicit join notation (using ,). It is considered as deprecated since the ANSI-92 standard (more than 20 yers ago!) and some vendors start dropping its support (MS SQL Server 2008; I don't know if there is a deprecation warning for this "feature" with Oracle?).
So, as a newcomer, you shouldn't learn bad habits from the start.
With the "modern" syntax, your query should be written:
SELECT e.name,
d.department_name,
SUM(j.salary)
FROM employees e
JOIN departments d USING(department_id)
JOIN jobs j USING(job_id)
WHERE d.department_name = 'M'
AND TO_CHAR(j.hire_date, 'YYYY') = '2002'
GROUP BY e.name, d.department_name;
With that syntax, there is a clear distinction between the JOIN relation (USING or ON) and the filter clause WHERE. It will later ease things when you will encounter "advanced" joins such as OUTER JOIN.
Yes, you just need a simple inner JOIN between all three tables.
SELECT SUM(salary)
FROM JOBS j
JOIN EMPLOYEES e ON j.job_id = e.job_id
JOIN DEPARTMENTS d ON e.department_id = d.department_id
WHERE d.department_name = 'M'
AND e.hire_date = 2002
I am currently learing SQL and I can't understand why these two queries return different numbers of rows (the first one returns 53, while the second returns 69).
SELECT d.department_name "dept_name",
j.job_title "job_title",
e.manager_id "manager_id",
MAX(e.salary) "max_salary",
SUM(e.salary) "sum_salary"
FROM employees e, jobs j, departments d
WHERE j.job_id = e.job_id AND
d.department_id(+) = e.department_id
GROUP BY GROUPING SETS( (d.department_name, j.job_title), (j.job_title,e.manager_id), ());
And the second one:
SELECT d.department_name AS "dept_name",
j.job_title AS "job_title",
e.manager_id AS "manager_id",
MAX(e.salary) AS "max_salary",
SUM(e.salary) AS "sum_salary"
FROM employees e
INNER JOIN jobs j ON j.job_id = e.job_id
RIGHT OUTER JOIN departments d ON d.department_id = e.department_id
GROUP BY GROUPING SETS( (d.department_name, j.job_title), (j.job_title,e.manager_id), ());
Thank you for your help!
Your queries differ because in the first one you outer join table departments, whereas in the second you outer join jobs and employees. (Which is why I don't like right outer joins so much. I don't find them very readable.) You take employees, join with jobs and then by RIGHT OUTER JOIN you say: and give me all departments anyhow, so give me additional (outer joined records) for jobs and employees. The second statement is equal to:
SELECT d.department_name AS "dept_name",
j.job_title AS "job_title",
e.manager_id AS "manager_id",
MAX(e.salary) AS "max_salary",
SUM(e.salary) AS "sum_salary"
FROM departments d
LEFT OUTER JOIN employees e ON e.department_id = d.department_id
LEFT OUTER JOIN jobs j ON j.job_id = e.job_id
GROUP BY GROUPING SETS( (d.department_name, j.job_title), (j.job_title,e.manager_id), ());
In old Oracle syntax you would write:
SELECT d.department_name "dept_name",
j.job_title "job_title",
e.manager_id "manager_id",
MAX(e.salary) "max_salary",
SUM(e.salary) "sum_salary"
FROM employees e, jobs j, departments d
WHERE e.department_id(+) = d.department_id
AND j.job_id(+) = e.job_id;
In old-style joins the syntax
FROM employees e, departments d
WHERE d.department_id(+) = e.department_id
is a left join and is equal to:
FROM employees e
LEFT JOIN departments d ON d.department_id = e.department_id
For these queries two work exactly the same, you should also change the RIGHT JOIN to LEFT JOIN in the second query, or d.department_id(+) = e.department_id to d.department_id = e.department_id(+) in the first.
I need to write a query that contains a subquery where it would list the name of departments and the number of employees per department having the word 'Representative' in their job_title and the list must be ordered by department_id.
I've written this query
SELECT d.department_name, emp.employee_id
FROM departments d, employees emp, jobs j
WHERE emp.department_id=d.department_id
AND j.job_title LIKE '%Representative%';
If there is a requirement to use a subquery, then the following will achieve what you're looking for:
select d.department_id,
d.department_name,
(select count(*)
from employees emp
join jobs j
on j.job_id = emp.employee_job -- I've made some assumptions, here!
where emp.department_id = d.department_id
and j.job_title like '%Representative%') reps
from departments d
order by d.department_id;
Personally, however, I would use a query like this:
select d.department_id,
d.department_name,
count(emp.employee_id) reps
from departments d
join employees emp
on emp.department_id = d.department_id
join jobs j
on j.job_id = emp.employee_job -- Same assumption as before!
where j.job_title like '%Representative%'
group by d.department_id,
d.department_name
order by d.department_id;
I find it easier to read/interpret, but that's ultimately up to you.
You don't need a subquery for this. Use a simple JOIN.
SELECT d.department_name, COUNT(*) AS cnt
FROM employee e JOIN department d
ON e.department_id = d.department_id
JOIN jobs j ON e.jobid = j.jobid
WHERE j.job_title LIKE '%Representative%'
GROUP BY d.department_name
Or, if it is just an exercise for you, I would suggest using the following query:
SELECT d.department_name
, (SELECT COUNT(e.*)
FROM employees e JOIN jobs j ON e.jobid = j.jobid
WHERE e.department_id = d.department_id
AND j.job_title LIKE '%Representative%') AS cnt
FROM departments d
In the real world, however, you code for convenience, not just for exercise. Your code should be convenient to read, understand and maintain for all those who are involved in your software development process. If it is just an exercise then you can use the second query. But if you have to use the query in a live application, the approach in the first query is better for everyone around you.
There is some join logic missing in your example, but something like this may work for you:
select d.department_name, count(emp.employee_id)
from departments d, employees emp, jobs j
where j.job_title in (select job_title from jobs where job_title like '%Representative%')
group by d.department_name
The SQL may not be 100% correct but you can see the point. it's hard to complete it without all of the join logic.
This should return you all of the department names and the employee count where the employee job title contains representative.