Understanding NOT EXISTS in SQL - sql

I am a beginner in SQL. I am now studying EXISTS and NOT EXISTS.
Question:
Retrieve the names of each employee who works on ALL projects
controlled by department 5.
Answer:
select fname, lname
from employee
where not exists ( (select pnumber from project where dnum = 5)
MINUS
(select pno from works_on where essn = ssn)
);
The first select in subquery gives (1,2,3) and the second select gives me (1,2,3,10,20,30). So (1,2,3) - (1,2,3,10,20,30) = 0. How does this solve the query? I am not sure if my logic is correct or the way I approach the problem.
Can someone please help me understand the solution step by step and visually if possible? thanks
Link to the course

So, the original problem was to:
Retrieve the names of each employee who works on ALL the projects controlled by department 5.
The provided answer makes use of the equivalence of:
All x such that f(x)
No x such that not f(x)
To put that in English, the problem is equivalent to finding those employees for whom there is no project controlled by department 5 that the employee doesn't work on.
So, first find all the projects controlled by department 5, then remove from that any project that the employee works on. That's exactly what the provided answer is doing. If there is nothing left, then there is no project controlled by department 5 that the employee doesn't work on. So by the equivalance, the employee works on all the projects controlled by that department.
While this is technically correct, it can feel a little odd. Especially if it were the case that department 5 controls zero projects. If that were true, the query would return all the employees ... which might not be quite what was expected.

Firstly, MINUS is an Oracle term. SQL-Server uses EXCEPT. Please read https://blog.sqlauthority.com/2008/08/07/sql-server-except-clause-in-sql-server-is-similar-to-minus-clause-in-oracle/ for more information.
Secondly, EXISTS returns either a TRUE or a FALSE result based on whether or not the parameter statement returns any records. For example, EXISTS ( SELECT * FROM Employee WHERE Fname = 'John' ) will return TRUE whereas EXISTS ( SELECT * FROM Employee WHERE Fname = 'Slartibartfast' ) will return FALSE.
https://www.w3schools.com/sql/sql_exists.asp gives a good explanation of EXISTS (with examples).
The EXISTS subquery does not need to refer to the main statement - it just needs to return at least one record in the same circumstances as when you want EXISTS to return TRUE. For instance...
SELECT *
FROM Employee
WHERE Dno = 5
AND EXISTS ( SELECT Sex
FROM employee
WHERE Sex = 'M'
AND Dno = 5 )
This query will return all records from Employee (irrespective of their Sex) for Dno 5 if that Department has at least one person with Sex = 'M'.
As for NOT EXISTS...
SELECT *
FROM Employee
WHERE Dno = 5
AND NOT EXISTS ( SELECT Sex
FROM employee
WHERE Sex = 'M'
AND Dno = 5 )
This query will return all records from Employee (irrespective of their Sex) for Dno 5 if that Department has no person with Sex = 'M'.
If you have any questions or comments, then please feel free to post a Comment accordingly.

Related

How to replace a column value for the matching records within a table in Oracle

I have a table which has some matching values of employees i.e. an employee could be in multiple departments.
I wanted to identify those records on the basis of their "name" and "dob". if it is same then replace the "id" as an increment of the decimal.
In below example: Mike is in 2 departments (IT, Finance) so I want his IT dept id (as an increment of the decimal) in the final outcome. Base id can identified on the basis department IT.
Please let me know how can I do this?
Let's take the min id and the row number divided by 10:
SELECT
MIN(id) OVER(PARTITION BY name, dob) + ((ROW_NUMBER() OVER(PARTITION BY name, dob ORDER BY id)-1)/10 as id,
name,
dob,
department
FROM
emp
I chose the min id for the employee as the base id. If you have a different strategy, like you want IT dept id to form the base value, instead of MIN(id) consider something like FIRST_VALUE(id) OVER(PARTITION BY ... ORDER BY case when department = 'it' then 0 else 1 end)
I agree with tim though; there seems a good deal of your question thatvis unclear poorly specified or not completely thought through. What if an emp is in 10 departments and an id conflict occurs? Generally we don't care about what an id number is so we don't change it or try to fill up gaps etc

Aggregate one table twice on different level

I am a little bit a newbie in SQL and am struggling with a seemingly easy task.
Let's see the data:
FirstName LastName ID DepartmentNumber ManagerID
Aliana Abramova 1111111111 4 4610226861
Boriana Borova 2222222222 4 4610226861
Cali Moldovanska 3333333333 4 4610226861
Anelia Simeonova 4009016246 1 4009016246
Maria Tacheva 4206174562 3 4206174562
This is an employee table. What I am trying to do is to extract these employees that are managers (ID = ManagerID) but only these out of them that work in a department that have more than one employees (so only these that have a count of ID grouped by DepartmentNumber >0)
I can do this tasks separately:
Select FirstName, LastName, ID
from Employee
where ID = ManagerID;
Select count(ID)
from Employee
group by DepartmentNumber;
It is hard for me though, to somehow merge this knowledge into one query and combined the data so that I know which are these IDs that belong to employees that are both managers and in their department work more than 1 person.
I have done similar tasks but when it comes to merging 1-2-3 tables grouped on different levels (and merged by different keys) I get somehow confused. Probably I need to make an interim select statement but now sure how.
You can use EXISTS:
SELECT *
FROM dbo.YourTable A
WHERE EXISTS(SELECT 1 FROM dbo.YourTable
WHERE DepartmentNumber = A.DepartmentNumber
GROUP BY DepartmentNumber
HAVING COUNT(*) > 1)
AND ID = ManagerID;

How to improve user defined function with while on SQL Server?

I have a SQL Server 2008 R2 UDF which performs a kind of recursive loop. I mean, I have a table called Employees where in one of my columns I store another Employee id (his boss).
When I get an employee id, I must be able to know the whole department below him. For example:
Employee Joe (ID:1) works for Robert (ID:2)
Employee Robert (ID:2) works for Michelle (ID:3)
I must be able to count the salary (let's suppose it's on the same table) of all employees below Michelle, i.e. Robert and Joe.
Up to now, I created a UDF that returns a table with all employee ids below Michelle and use an EXISTS clause on the queries' where but it performs very poorly.
Do you guys have another idea?
Thank you!
You should probably use a recursive CTE rather than a WHILE loop to find all of the employees. I don't have your tables or data so I've made some up:
create table Employees (
ID int not null primary key,
Name varchar(20) not null,
BigBossID int null foreign key references Employees(ID),
Salary decimal(18,4) not null
)
go
insert into Employees (ID,Name,BigBossID,Salary) values
(1,'Joe',2,2.50),
(2,'Robert',3,19000.75),
(3,'Michelle',null,1234567890.00)
And then I can use this query to find all employees below Michelle:
declare #RootID int
set #RootID = 3
;With EmployeesBelowRoot as (
select ID from Employees where BigBossID = #RootID
union all
select e.ID from Employees e inner join EmployeesBelowRoot ebr on e.BigBossID = ebr.ID
)
select SUM(Salary) from Employees where ID in (select ID from EmployeesBelowRoot)
You could (if you think it's worth it) place the CTE (EmployeesBelowRoot) into a UDF and call it with #RootID as a parameter, but I've just put it directly in the query for now.

How to design/create rules table with criteria in SQL Server 2008

I don't have much experience with SQL Server and I'm pretty much lost when it comes to anything more advanced than simple INSERT/SELECT statements.
My task is to create table which hold 'criteria' (rules) for other tables in same DB. I mean we have an Employee table, a Salary table etc. I need to make some rules between Employee table and Salary table by its values, i.e. if Employee = John and Salary = 2000 then make 1 criteria, if Employee = Steve and Salary = 3000 then make 2 criteria.
I have created a table called Rules:
RuleID Criteria
--------------------------------------------------
1 Employee = John and Salary = 2000
2 Employee = Steve and Salary = 3000**
My question: As mentioned above is correct way to create 'criteria' table? (maybe I have 50 different conditions...Would it be correct way If I create table with all conditioned as mentioned above?) Please advise.
I am sorry If I am not able to explain it in correct way or asking basic question.
I would appreciate any help in this. Thanks a lot in advance..
We have created an effective Rules Engine that notifies the appropriate user(s) for a given rule. Our columns are as follows: RuleID, RuleName, SequenceNum, IsActiveFlag, RuleRunDays, DataSteward, CarbonCopyRecipients, NotificationLevel, RuleSQL, NotificationSubject, NotificationText, HTMLFlag
The key is to have a column that is a valid executable SQL statement (as RuleSQL above). Here is an example of a value you might put in the RuleSQL column:
SELECT ErrorMessage = 'Salary cannot be null for Active employees.'
, e.EmployeeName , es.Salary , ...
FROM EmployeeSalary es
JOIN Employee e
ON e.EmployeeId = es.EmployeeId
WHERE es.Salary IS NULL AND e.Status = 'Active'

SQL BETWEEN not working properly

I am using Microsoft SQL Server
I am trying to use the following sql command to get the records between First_Name "Nilsen" and "Ram"
select * from persons where First_Name between 'Nilsen' and 'Ram'
but i am getting the output as two records with first names "nilsen" and "ram"; not the records between these records.
In another command, i tried doing similar thing with Last names.
select * from persons where Last_Name between 'Johan' and 'Chandra'
this command shows just a blank persons table.
Please tell me its not working properly.
This query:
SELECT *
FROM persons
WHERE First_Name between 'Nilsen' and 'Ram'
will return all entries with First_Name alphabetically between Nilsen and Ram (like Oscar, Rachel or Norbert)
This query:
SELECT *
FROM persons
WHERE Last_Name between 'Johan' and 'Chandra'
will never return anything since Johan is greater than Chandra (i. e. goes later in alphabetical order).
Update:
Just a wild guess: if you want to match something like Nilsen Hermenegild J. P. Ram, Jr., you need to use this:
SELECT *
FROM persons
WHERE FirstName LIKE '%Nilsen%Ram%
This is how BETWEEN works, from MSDN
BETWEEN returns TRUE if the value of
test_expression is greater than or
equal to the value of begin_expression
and less than or equal to the value of
end_expression.
so
select * from persons where First_Name between 'Nilsen' and 'Ram'
should return records with first names 'Nilsen' and 'Ram', plus the records between.
Just guessing, try this:
select * from persons where lower(First_Name) between 'nilsen' and 'ram'