SELECT clause with MINUS INTERSECT and UNION - sql

In my database, i created a table name DEPTLOC
//DEPTLOC
DNAME CITY
----------------------------
RESEARCH BOSTON
IT PARIS
SCIENCE LONDON
RESEARCH LONDON
SALES NEWYORK
RESEARCH PARIS
RESEARCH NEWYORK
MARKETING NEWYORK
So i used the following query
SELECT CITY FROM DEPTLOC
INTERSECT
(
SELECT CITY FROM DEPTLOC WHERE DNAME='SALES'
UNION
SELECT CITY FROM DEPTLOC WHERE DNAME='RESEARCH'
);
But my output is all the CITY will be displayed. My question is want find which of the DNAME='SALES' OR DNAME='RESEARCH' has its location in all cities.
So from the table above, all distinct city are
CITY
-------
BOSTON
PARIS
LONDON
NEWYORK
Since 'RESEARCH' have all the location but 'SALES' Only have some, my output should be display like this
DNAME
---------
RESEARCH
What should i change for my query in order to get the correct output

Here's a shorter (than my previous answer) query that checks only between DNAMES SALES and RESEARCH to see which DNAME has all CITIES. Set operators UNION ALL and MINUS are used.
SELECT DNAME
FROM
(
SELECT 'SALES' DNAME, COUNT(*) MISSING_CITIES
FROM
(
SELECT DISTINCT CITY FROM DEPTLOC
MINUS
SELECT CITY FROM DEPTLOC WHERE DNAME = 'SALES'
)
UNION ALL
SELECT 'RESEARCH', COUNT(*) FROM
(
SELECT DISTINCT CITY FROM DEPTLOC
MINUS
SELECT CITY FROM DEPTLOC WHERE DNAME = 'RESEARCH'
)
)
WHERE MISSING_CITIES = 0;

One way of doing this is to count the distinct locations, and join it on the departments query:
SELECT dname
FROM (SELECT dname, COUNT(*) AS dept_city_count
FROM deptloc
GROUP BY dname) d
JOIN (SELECT COUNT (DISTINCT city) AS city_count
FROM deptloc) ON city_count = dept_cirt_count

Here is a query using joins. First all Cities are selected. Then, a CROSS JOIN is made between all Departments and all Cities. Then, a LEFT JOIN is made to select Departments that do not have all Cities. Then, another LEFT JOIN is made to select Departments that do have all Cities.
SELECT DISTINCT
DL.DNAME
FROM
DEPTLOC DL
LEFT JOIN
(
SELECT
ALL_COMBOS.DNAME dname
FROM
(
SELECT DISTINCT
D1.DNAME DNAME,
D2.CITY CITY
FROM
DEPTLOC D1
CROSS JOIN
(
SELECT DISTINCT
CITY
FROM
DEPTLOC
)
D2 --All distinct Cities
)
ALL_COMBOS --All Departments with all Locations
LEFT JOIN DEPTLOC D2
ON
ALL_COMBOS.DNAME = D2.DNAME
AND ALL_COMBOS.CITY = D2.CITY
WHERE
D2.DNAME IS NULL
)
NOT_ALL_LOCATIONS --Departments that do not have all Locations
ON DL.DNAME = NOT_ALL_LOCATIONS.DNAME
WHERE
NOT_ALL_LOCATIONS.DNAME IS NULL; --Departments that have all Locations
As it can be observed, this method does not rely on the count of the Cities, but on the actual values of the Cities themselves.

Assuming that the key in deptloc is (dname, city) then the following would work:
select dname
from deptloc
group by dname
having count(*) = (select count(distinct city) from deptloc);
The query works by creating one group for every department. The count(*) would be the nr of cities that the department is located in. Each such count is compared with the nr of unique Cities cited in the same table.
Edited: Saw your comment now, that this was the solution you did not want :)
Here is another version of "relational division"
select departments.dname
from (select distinct dname from deptloc) departments
where not exists(
select 'x'
from (select distinct city from deptloc) cities
where not exists(
select 'x'
from deptloc x
where x.city = cities.city
and x.dname = departments.dname));

Related

Display count of subquery in outer query

I want to see the number of departments from Query2 as a new column in Query1. How can I do that?
Query 1:
SELECT
location_id,
street_address,
postal_code,
city,
state_province,
country_id
FROM
locations;
Query 2:
SELECT
location_id,
COUNT(department_id)
FROM
departments
group by location_id;
One method is a correlated subquery:
select l.*,
(select count(*) from departments d where d.location_id = l.location_id
) as num_departments
from locations l;

SQL MAX() grouping

Tables:
student(sid, sname, sex, age, year, gpa)
major(dname, sid)
Question:
For each department with more than 15 students majoring in the department, we want to print information about the student(s) with the highest GPA within the department. In particular, for each such student, we want to print the student id, student name and GPA, and the department name the student is major in.
So far I have:
SELECT student.sid, student.sname, student.gpa, major.dname
FROM student
RIGHT JOIN major ON student.sid = major.sid
WHERE student.gpa IN (
SELECT MAX(gpa)
FROM student JOIN major ON student.sid = major.sid
GROUP BY dname
HAVING COUNT(dname) > 15
)
But it doesn't give me the accurate query. The clause inside the IN works but when put together in this way it doesn't actually match student.gpa to dname max GPA. What am I doing wrong here?
This Query gives:
enter image description here
I need:
enter image description here
Your inner query gives you the maximum GPA for each department. Then the outer query returns all students who have a GPA equal to any of the maximum GPA's, regardless of the department. The quickest fix of your code is to use a correlated subquery, that will find the maximum GPA for the student's specific department.
SELECT s.sid, s.sname, s.gpa, sm.dname
FROM student s
RIGHT JOIN major sm ON s.sid = sm.sid
WHERE student.gpa IN (
SELECT MAX(ds.gpa)
FROM student ds JOIN major dm ON ds.sid=dm.sid
WHERE dm.dname = sm.dname
GROUP BY dm.dname
HAVING COUNT(dm.dname)>15
)
This query:
select dname, max(s.gpa) maxgpa
from major m inner join student s
on s.sid = m.sid
group by dname
having count(s.sid) > 15
returns all the departments with more than 15 students and the highest gpa in that department.
Join it to the 2 tables like this:
select s.sid, s.sname, s.gpa, t.dname
from (
select dname, max(s.gpa) maxgpa
from major m inner join student s
on s.sid = m.sid
group by dname
having count(s.sid) > 15
) t
inner join major m on m.dname = t.dname
inner join student s on s.sid = m.sid and s.gpa = t.maxgpa
Or with window functions:
select t.sid, t.sname, t.gpa, t.dname
from (
select m.dname, s.*,
rank() over (partition by m.dname order by s.gpa desc) rn,
count(s.sid) over (partition by m.dname) counter
from major m inner join student s
on s.sid = m.sid
) t
where t.counter > 15 and t.rn = 1
If you change your WHERE ... IN statement to be an INNER JOIN, you can connect more fields.
SELECT student.sid, student.sname, student.gpa, major.dname
FROM student
RIGHT JOIN major
ON student.sid = major.sid
INNER JOIN (
SELECT MAX(gpa) as max_gpa, dname
FROM student JOIN major ON student.sid=major.sid
GROUP BY dname
HAVING COUNT(dname)>15
) as dept_gpa
ON student.gpa = dept_gpa.max_gpa
AND major.dname = dept_gpa.dname

How to get the department name in this tutorial sql query?

Following the tutorial on SQL here I want to query the number of employees per department together with the department name.
I tried the following query in that tutorial:
SELECT count(*), dept_name
FROM employees, departments
WHERE employees.dept_id = departments.dept_id
GROUP BY departments.dept_id
but it returns
COUNT(*) dept_name
2 NULL
2 NULL
instead of the expected output
COUNT(*) dept_name
2 Accounting
2 Sales
What am I doing wrong here?
First use JOIN instead of WHERE
Then you group by dept_id to make sure you dont have duplicate name like 2 Sales department or 2 employee with same name.
SELECT departments.dept_id, dept_name, count(*)
FROM employees
JOIN departments
ON employees.dept_id = departments.dept_id
GROUP BY departments.dept_id, departments.dept_name
Group by dept_name not dept_id
SELECT count(*), dept_name
FROM employees, departments
WHERE employees.dept_id = departments.dept_id
GROUP BY departments.dept_name
And you can better use join like Juan Carlos Oropeza's answer:
SELECT count(*), dept_name
FROM employees JOIN departments ONemployees.dept_id = departments.dept_id
GROUP BY departments.dept_name

SQL query issue with achieving an encompasses all effect

Below is the query I have been working on for this question.
Find the names of the companies which have employees residing in every city where employees of Mutual of Omaha live.
This means that if Mutual has employees in the cities Omaha, Lincoln, and Denver that the only company names it should return is a company that has employees in all 3 of those cities. This should also return Mutual.
The below query returns the company which has an employee in any of those three cities. The lastname is there for me to manually check which employees it is counting.
SELECT COMPANY_NAME, e1.lastname
FROM EMPLOYEE E1,WORKS W1
WHERE E1.CITY IN (SELECT CITY
FROM EMPLOYEE E2,WORKS W2
WHERE E2.firstname = W2.firstname
AND E2.lastname = W2.lastname
AND W2.COMPANY_NAME= 'Mutual of Omaha')
AND E1.firstname = W1.firstname
AND E1.lastname = W1.lastname;
I realized I didn't put the tables down so here they are
employee (Lastname, FirstName, MidInitial, gender, street, city)
works (Lastname, FirstName, MidInitial, company_name, salary)
manages(Lastname, FirstName, MidInitial, ManagerLastname, MFirstName, MMidInitial, start-date)
It's not the most elegant piece of code, but try using this one:
WITH tab AS (
SELECT DISTINCT W1.COMPANY_NAME,
E1.CITY
FROM EMPLOYEE E1
JOIN WORKS W1 ON (E1.firstname = W1.firstname AND E1.lastname = W1.lastname)
WHERE E1.CITY IN (SELECT DISTINCT E2.CITY
FROM EMPLOYEE E2,WORKS W2
WHERE E2.firstname = W2.firstname
AND E2.lastname = W2.lastname
AND W2.COMPANY_NAME= 'Mutual of Omaha')
)
SELECT tab.COMPANY_NAME
FROM tab
GROUP BY tab.COMPANY_NAME
HAVING COUNT(tab.CITY) = (SELECT COUNT(sub.CITY) FROM tab sub WHERE COMPANY_NAME = 'Mutual of Omaha')
This option uses the bitwise operator so is also limited by the number of cities it can use at once (~31 due to Int size limit)
------------Assign each distinct city a value that is x2 the previous (like binary counting)
create table #cityvalues (CityName varchar(100), ValueField int)
select distinct E1.CITY
into #while
FROM EMPLOYEE E1
while (select count(*) from #while) > 0
begin
insert into #cityvalues
select top 1 CITY, coalesce((select max(ValueField) from #cityvalues)*2, 1) from #while
delete from #while w where w.CITY in (select CityName from #CityValues)
end
--------------------------------------------------------------------------------
--------------Create a list of Company/City--------------------------------
create table #companycities (Compname varchar(100),CityName varchar(100))
insert into #companycities
select distinct
W1.COMPANY_NAME
,E1.CITY
FROM EMPLOYEE E1
JOIN WORKS W1 on E1.firstname = W1.firstname AND E1.lastname = W1.lastname
----------------------------------------------------------------------------
----This SUM function then creates a "list" of all cities for the company in a single field
select cc.Compname, sum(cv.ValueField) as AllCities
into #CompanyAllCities
from #companycities cc
join #cityvalues cv on cc.CityName = cv.CityName
group by Compname
-----------------------------------------------------------------
----------This query checks if the company's "list" contains the "list" for the joined company, excluding itself
select distinct cac1.Compname
from #CompanyAllCities cac1
join #CompanyAllCities cac2 on cac2.Compname = 'Mutual'
where cac2.AllCities & cac1.AllCities = cac2.AllCities
and cac1.Compname <> 'Mutual'
-------------------------------------------------------------
----------Tidy up after yourself----------
drop table #cityvalues,#CompanyAllCities,#companycities
-----------------------------------------
I would start with a CTE that has each company with the cities they operate in.
Then there are several options, but a self-join with aggregation does the count that you want:
with cw as (
select distinct e.city, w.company_name
from employee e join
works w
on e.firstname = w.firstname and
e.lastname = w.lastname and
e.midinitial = w.midinitial
)
select cw.company_name
from cw join
cw cwo
on cw.city = cwo.city and
cwo.company_name = 'Mutual of Omaha'
group by cw.company_name
having count(*) = (select count(*) from cw where
cw.company_name = 'Mutual of Omaha');
You can list and weight the cities where companies have employees, so you will get a single number representing each company based on where they have their employees, a sort of group number.
Then you can check which cities have the same group number.
;with
cities as(select ROW_NUMBER() over (order by city) city_id, city from (select distinct city from employees) c),
chk as (
select distinct company_name, city_id
from works w
join employees e on w.firstname = e.firstname and w.lastname = e.lastname
join cities c on c.city = e.city
),
cnt as (
select company_name, SUM(power(cast(2 as bigint), city_id-1)) n
from chk
group by company_name
)
select company_name
from cnt
where n = (select n from cnt where company_name = 'Mutual of Omaha')

Either in not both clause in select sql

Find the names of all departments located either in BOSTON or in DALLAS" and not in both cities.
I having the code like this
SELECT D.DNAME
FROM DEPARTMENT D
INNER JOIN DEPTLOC L ON L.DNAME = D.DNAME
WHERE L.CITY='BOSTON'
OR L.CITY='DALLAS' ;
But this will show the department that located in BOSTON OR DALLAS . But i just want either in, what should i put in order to get the result.
Example:
in my DEPTLOC TABLE
//DEPTLOC
DNAME CITY
----------------
ACCOUNTING BOSTON
ACCOUNTING DALLAS
SALES DALLAS
TRANSPORT BOSTON
TRANSPORT DALLAS
So in my DEPARTMENT
i should get output like
DNAME
----------
SALES
Group them, then calculate the total count for each departments, then filter all departments which has only one location.
SELECT D.DNAME
FROM DEPARTMENT D
INNER JOIN DEPTLOC L ON L.DNAME = D.DNAME
WHERE L.CITY='BOSTON'
OR L.CITY='DALLAS'
GROUP BY
D.DNAME
HAVING COUNT(1) = 1
You could write:
SELECT department.dname
FROM department
JOIN deptloc
ON department.dname = deptloc.dname
WHERE deptloc.city IN ('BOSTON', 'DALLAS')
GROUP
BY department.dname
HAVING COUNT(DISTINCT deptloc.city) = 1
;
For that matter, since each value in deptloc.dname presumably also occurs in department.name, you can dispense with the join and just write:
SELECT dname
FROM deptloc
WHERE city IN ('BOSTON', 'DALLAS')
GROUP
BY dname
HAVING COUNT(DISTINCT city) = 1
;
Try something like this
--take only those that ara in one city
SELECT DNAME_WITH_COUNT.DNAME FROM
--count how many times it occurs
(SELECT DNAME, COUNT(DNAME) CNT FROM
--your select with both cities
(SELECT D.DNAME
FROM DEPARTMENT D
INNER JOIN DEPTLOC L ON L.DNAME = D.DNAME
WHERE L.CITY='BOSTON'
OR L.CITY='DALLAS'
)
)DNAME_WITH_COUNT
WHERE CNT>1;
Try this:
SELECT D.DNAME
FROM DEPARTMENT D
INNER JOIN DEPTLOC L ON L.DNAME = D.DNAME
GROUP BY D.DNAME
HAVING 1 = SUM(CASE WHEN L.CITY IN ('BOSTON', 'DALLAS') THEN 1 ELSE 0 END);
You could join the department table with an aggregate query that returns only one location per department:
SELECT d.dname
FROM department d
INNER JOIN (SELECT dname
FROM deptloc
WHERE city IN ('BOSTON', 'DALLAS')
GROUP BY dname
HAVING COUNT(*) = 1) l ON l.dname = d.dname