Finding an ID not in another column - sql

I'm working on a little SQL exercise, and am scratching my head an this problem.
I am trying to find all the Employees to whom no other employee reports to.
This is what the employees table looks like:
EmployeeId LastName FirstName Title ReportsTo
1 Adams Andrew General Manager null
2 Edwards Nancy Sales Manager 1
3 Peacock Jane Sales Support Agent 2
4 Park Margaret Sales Support Agent 2
5 Johnson Steve Sales Support Agent 2
6 Mitchell Michael IT Manager 1
7 King Robert IT Staff 6
8 Callahan Laura IT Staff 6
I thought a straightforward one of these queries would do it:
SELECT *
FROM employees
Where EmployeeId not in (select ReportsTo from employees)
SELECT *
FROM employees
Where EmployeeId not in (ReportsTo)
But those return the following results, which isn't what I'm looking for:
EmployeeId LastName FirstName Title ReportsTo
2 Edwards Nancy Sales Manager 1
3 Peacock Jane Sales Support Agent 2
4 Park Margaret Sales Support Agent 2
5 Johnson Steve Sales Support Agent 2
6 Mitchell Michael IT Manager 1
7 King Robert IT Staff 6
8 Callahan Laura IT Staff 6
Why is NOT IN returning items that are definitely in that column? How would I go about returning items not in ReportsTo if I am using NOT IN incorrectly?

Use not exists with a correlated subquery (as commented by jarlh):
select *
from employees e
where not exists (
select 1
from employees e1
where e1.ReportsTo = e.EmployeeId
)

The problem with your 1st query is that you use NOT IN with a list that contains a NULL value.
So a comparison of an EmployeeId like say 5:
5 NOT IN (null, 1, 2, 6)
will return NULL, because any comparison to NULL returns NULL and that EmployeeId will not be included in the results.
Change to:
SELECT *
FROM employees
Where EmployeeId not in (
select ReportsTo
from employees
where ReportsTo is not null
);
See the demo.
Results:
| EmployeeId | LastName | FirstName | Title | ReportsTo |
| ---------- | -------- | --------- | ------------------- | --------- |
| 3 | Peacock | Jane | Sales Support Agent | 2 |
| 4 | Park | Margaret | Sales Support Agent | 2 |
| 5 | Johnson | Steve | Sales Support Agent | 2 |
| 7 | King | Robert | IT Staff | 6 |
| 8 | Callahan | Laura | IT Staff | 6 |

You can simply use the below query--
select * from employees emp where employeeID not in (select ReportsTo from employee)

Related

Finding duplicates between two columns same table

I want to find duplicate values between two columns without a id.
Example:
table employees
-----------------------------------
employee_one | employee_two |
-----------------------------------
JOHN SMITH | JACK STEVENS |
MASON LEWIS | JOHN WALKER |
ANDREA YOUNG | MARTINA ROBINSON|
JACK STEVENS | JOHN SMITH |
JOHN WALKER | MASON LEWIS |
MARTINA ROBINSON| ANDREA YOUNG |
and the results I want is:
-----------------------------------
employee_one | employee_two |
-----------------------------------
JOHN SMITH | JACK STEVENS |
MASON LEWIS | JOHN WALKER |
ANDREA YOUNG | MARTINA ROBINSON|
or
-----------------------------------
employee_one | employee_two |
-----------------------------------
JACK STEVENS | JOHN SMITH |
JOHN WALKER | MASON LEWIS |
MARTINA ROBINSON| ANDREA YOUNG |
My problem is that my query always find all the results and I get the same table. I tried:
SELECT DISTINCT t1.*
FROM employees
AS t1 LEFT JOIN employees AS t2 ON (t1.employee_one = t2.employee_two AND t1.employee_two = t2.employee_one)
OR (t1.employee_one = t2.employee_one AND t1.employee_two = t2.employee_two)
But I get the same results
Every pair of names will show up twice, so use a where clause to limit the output to just those where employee_one < employee_two:
select t1.*
from employees t1
where employee_one < employee_two
and exists (
select *
from employees t2
where t2.employee_two = t1.employee_one
and t2.employee_one = t1.employee_two)
Caveat: this assumes that there are no rows where employee_one = employee_two.

SQL: Select groups that do not contain a certain value

I use Microsoft SQL Server Management Studio 2014 and I have these 3 tables:
EMPLOYEES
EMPID | FIRSTNAME
1 | JOHNNY
2 | DWAYNE
3 | TOM
4 | CHRISTIAN
5 | JACK
6 | BRAD
7 | ADAM
8 | MATT
9 | WILL
10 | JIM
AIRCRAFTS
AID | NAME
1 | BOEING 1
2 | BOEING 2
3 | BOEING 3
4 | BOEING 4
5 | AIRBUS 1
6 | AIRBUS 2
7 | LEARJET
8 | DOUGLAS
9 | JUMBO
10 | ILYUSHIN
CERTIFIED
EMPID | AID
1 | 1
1 | 2
1 | 3
1 | 4
4 | 2
4 | 3
7 | 1
7 | 2
7 | 5
7 | 6
8 | 7
8 | 8
8 | 9
2 | 10
2 | 1
2 | 9
3 | 10
5 | 8
5 | 9
The concept is that there are 10 employees and 10 aircrafts. The CERTIFIED table determines which employee is authorized to pilot which aircrafts. Not all employees are pilots though. What I need is to somehow select all pilots who are not certified to use a Boeing. What I tried but did not work is the following:
SELECT DISTINCT FIRSTNAME
FROM EMPLOYEES
WHERE EMPID IN (SELECT EMPID
FROM CERTIFIED
WHERE AID NOT IN (SELECT AID FROM AIRCRAFTS WHERE NAME LIKE 'BOEING%'))
Which gives these results:
JACK
MATT
TOM
ADAM
DWAYNE
This is wrong because according to the CERTIFIED table, ADAM and DWAYNE are authorized to pilot at least one Boeing.
Any help would be appreciated, thanks in advance!
Try this query...
SELECT employees.empid, Max(employees.firstname) AS FirstName
FROM certified
INNER JOIN employees ON employees.empid = certified.empid
WHERE certified.empid NOT IN (SELECT certified.empid
FROM certified
INNER JOIN aircrafts ON aircrafts.aid = certified.aid
WHERE aircrafts.NAME LIKE 'BOEING%')
GROUP BY employees.empid
Demo: http://www.sqlfiddle.com/#!18/8f26d/27/0
Result
+-------+-----------+
| EMPID | FirstName |
+-------+-----------+
| 3 | TOM |
| 5 | JACK |
| 8 | MATT |
+-------+-----------+
I think your query is giving any employee that is certified on a non-Boeing aircraft -- a subtly different set of people.
For your question, I would go for not exists:
select e.*
from employees e
where not exists (select 1
from certified c join
aircrafts a
on c.aid = a.aid
where e.empid = c.empid and a.name like '%BOEING%'
);
Another approach -- if you just want the employee id -- uses aggregation and having
select e.empid, e.firstname
from employees e join
certified c
on e.empid = c.empid join
aircrafts a
on c.aid = a.aid
group by e.empid, e.firstname
having sum(case when a.name like '%BOEING%' then 1 else 0 end) = 0;
I happen to like this method, because it generalizes very easily to other conditions -- such as flies Boeing but not Airbus or flies Learjet and Cessna.
You should use NOT IN the emp that are certified by Boeing in join with aircrafts
SELECT DISTINCT FIRSTNAME
FROM EMPLOYEES
WHERE EMPID NOT IN (SELECT EMPID
FROM CERTIFIED c
INNER JOIN AIRCRAFTS a ON on a.AID = c.AID
WHERE a.NAME LIKE 'BOEING%')

Group and sum on multiple ids

My brain has locked up on the following problem.
This for an Bi Publisher report
Users can select one or several employee names from a multi select dropdown
e.g John, Peter, Ann ...
In table Emp we have:
emp_id | emp_name
---
1 | John
2 | Peter
3 | Ann
Table worklimit
emp_id | limit | from_date | to_date
---
1 | 35 | 04-jul-2016 | 08-jul-2016
1 | 15 | 11-jul-2016 | 15-jul-2016
2 | 40 | 04-jul-2016 | 08-jul-2016
2 | 20 | 01-aug-2016 | 05-aug-2016
3 | 27 | 04-jul-2016 | 08-jul-2016
So the result I want is a total sum(limit) for the selected employee
e.g John + Ann 77
Or John + Ann + Peter = 137
This is just the first step. I need to sum up som values from a third table too, and I need to select a date range, and it must be grouped by week or month to make a barchart .
Can some of you clever brains out there point me in the right directions?
Try this:
SELECT SUM(A.limit) FROM
(
SELECT SUM(limit) limit,emp_name
FROM worklimit
JOIN Emp ON Emp.emp_id = worklimit.emp_id
GROUP BY emp_name
) A
WHERE emp_name IN ('John','Peter','Ann')

How to get a Max count for a category?

I have 2 tables that hold Empolyee and Office data:
EMPLOYEE (EmpID, Role, OfficeNumber)
OFFICE (OfficeNumber, Region, Manager)
Employees can be registered under multiple offices with different roles.
For example below the Employee is registered in 2 offices (20 & 50) but has more entries in office 20 so we want to select that as his "Home" Office
EmpID | Role | Region | Manager | OfficeNumber
15 | 1 | East | James | 20
15 | 4 | East | James | 20
15 | 5 | East | James | 20
15 | 1 | West | George | 50
15 | 5 | West | George | 50
Basically I want to get a count for which office the employee is registered under the most for that region, then return the manager for that office..
So for the above employee the result for their "Home Office" would be:
EmpID | Region | Manager | OfficeNumber
15 | East | James | 20
This is the SQL that I have put together.
SELECT
EmpID, Manager, Region,
Count (OfficeNumber),
Row_Number(Partition by EmpID ORDER by
Count(OfficeNumber) Desc)
FROM
Employees e
JOIN
Offices o ON e.OfficeNumber = o.OfficeNumber
Not sure if I'm using the Row_Number function properly or whether there would be a better approach?
Two CTEs can do the job. The 1st one creates the counts per Employee/Office and the second one gets the Main Office for each Employee. Hope it works 4 u
with CountCte as (
SELECT *,count(*) over (partition by [EmpID],[OfficeNumber]) OfficeCount
FROM
Employees e
JOIN
Offices o ON e.OfficeNumber = o.OfficeNumber),
MainCte as (
select *,row_number() over (partition by [EmpID] order by OfficeCount desc) r
from CountCte)
select EmpID
,Region
,Manager
,OfficeNUmber
from MainCte
where r = 1

Make one-to-many relationship look like one-to-one

I'm using postgres 9.3.5.
Given the following data:
select * from department;
id | name
----+-----------
1 | sales
2 | marketing
3 | HR
and
select * from people;
id | department_id | first_name | last_name
----+---------------+------------+-----------
1 | 1 | Tom | Jones
2 | 1 | Bill | Cosby
3 | 2 | Jessica | Biel
4 | 1 | Rachel | Hunter
5 | 2 | John | Barnes
I'd like to return a result set like this:
id | name | first_name-1 | last_name-1 | first_name-2 | last_name-2 | first_name-3 | last_name-3
----+-----------+--------------+-------------+--------------+-------------+--------------+------------
1 | sales | Tom | Jones | Bill | Cosby | Rachel | Hunter
2 | marketing | Jessica | Biel | John | Barnes
3 | HR |
Is this possible?
The answer provided here by Max Shawabkeh using the GROUP_CONCAT is close - but its not returning as extra fields in the dataset, its concatenating them into a single field.
You need cross-tabulation (sometimes called pivot).
Could look like this in your case:
SELECT * FROM crosstab(
$$SELECT d.id, d.name,'p' AS dummy_cat
,concat_ws(' ', p.first_name, p.last_name) AS person
FROM department d
LEFT JOIN people p ON p.department_id = d.id
ORDER BY d.department_id, p.id$$
)
AS ct (id int, department text, person_1 text, person_2 text, person_3 text);
Returns:
id department person_1 person_2 person_3
--------------------------------------------------------
1 sales Tom Jones Bill Cosby Rachel Hunter
2 marketing Jessica Biel John Barnes <NULL>
3 HR <NULL> <NULL> <NULL>
Very similar to this related case (explanation for special difficulties there):
Postgres - Transpose Rows to Columns
But this case is simpler: since you do not seem to care about the order in which persons are listed, you can use the basic one-parameter form of crosstab().
Also, according to your comment, you want all departments, even if no people are assigned. Adjusted the LEFT JOIN accordingly.
Basic details in this related answer:
PostgreSQL Crosstab Query