SQLite subqueries trying find if id does not exist in another column - sql

I have been battling with this for a bit. This is a test question from a testing site but I have no one to email and try find the answer from.
CREATE TABLE employees (
id INTEGER NOT NULL PRIMARY KEY,
managerId INTEGER REFERENCES employees(id),
name VARCHAR(30) NOT NULL
);
INSERT INTO employees(id, managerId, name) VALUES(1, NULL, 'John');
INSERT INTO employees(id, managerId, name) VALUES(2, 1, 'Mike');
-- Expected output (in any order):
-- name
-- ----
-- Mike
-- Explanation:
-- In this example.
-- John is Mike's manager. Mike does not manage anyone.
-- Mike is the only employee who does not manage anyone.
Write a query that selects the names of employees who are not managers.
This is what I have come up with but it does not work.
SELECT name
FROM employees
WHERE id NOT IN(SELECT managerId FROM employees)
I'm just trying to understand how I can iterate through the managerId column and check whether the Id matches it or not?

This is because one of the selected manager IDs is null. Null ist the "unknown value". So NOT IN does not succeed, as it cannot guarantee that your value is not in the data set (as your value could be the unknown value). Well, so far for the argument.
So either:
SELECT name
FROM employees
WHERE id NOT IN (SELECT managerId FROM employees WHERE managerId IS NOT NULL);
or
SELECT name
FROM employees e
WHERE NOT EXISTS (SELECT * FROM employees m WHERE m.managerId = e.id);
This is really a nasty trap one must be aware of. Most often we look up values that cannot be null. Bad luck yours is a rare case where nulls exist in the lookup :-)

Related

how to get emp id for any change values happend after first record

I have emp table
it contains many colmuns and an employee can have many rows beside on the
changed happend to his/her records . it hasnt primary key because emp id can repeated beside on employee values .
there's a column "health" ,it decribes the health and with values(heart,skin,null) etc.. and modification_date for each change of values in a health column
let's say employee number 1 has a heart problem as a first record registed in a health column
then the employee got well then added a second row and column health=null ,
after sometimes the employee got sick to a nother disease 'skin'
how to get employee number if his/her column(health)
has been change to any values of health if values become null or other values ?
any help please ?
select empid, health_status from
(
select e.emp_id empid, e.health health_status,
count(e.health) over (partition by e.emp_id order by e.modification_date asc) sick_count
from emp e
)
where sick_count > 1
Seems you need counting NULLs and NOT-NULLs. NVL2() function would suit well in order to compute this such as
SELECT e.emp_id, e.health,
SUM(NVL2(health,1,0)) OVER (PARTITION BY e.emp_id) AS "Sick",
SUM(NVL2(health,0,1)) OVER (PARTITION BY e.emp_id) AS "Got well"
FROM emp e
if the health is NOT-NULL then second argument will return, otherwise the third argument will return. Btw, using an ORDER BY clause will be redundant.
From Oracle 12, you can use MATCH_RECOGNIZE to find the employees who were sick, got well and then got sick again:
SELECT emp_id
FROM emp
MATCH_RECOGNIZE (
PARTITION BY emp_id
ORDER BY modification_date
PATTERN (sick well+ sick)
DEFINE
sick AS health IS NOT NULL
well AS health IS NULL
);

creating the sql query for company supervisors

I have four tables
create table emp (emp_ss int, emp_name nvarchar(20));
create table comp(comp_name nvarchar(20), comp_address nvarchar(20));
create table works (emp_ss int, comp_name nvarchar(20));
create table supervises (spv_ss int, emp_ss int );
Here SUPRVISER_SS and EMP_SS are subset of SS. Now I have to find:
the name of all the companies who have more than 4 supervisors
I have made a query for the above problem but not sure whether it is correct or not
SELECT COMP_NAME , COUNT(EMP_SS) FROM WORKS
WHERE EMP_SS IN (SELECT DISTINCT SPV_SS FROM supervises)
GROUP BY COMP_NAME
HAVING COUNT(EMP_SS) > 4;
the name of supervisors who have the largest number of employees
but unable to get the required result of the above condition
SELECT SPV_SS, COUNT(*) max_ FROM supervises GROUP BY SPV_SS
You don't need to have a seperate table for supervisors unless they come with extra information that doesn't belong in the employee table, just add an extra field (foreign key) in Employee table that links to the primary key in the same table.
First question: select company just use a group by companyid clause and then check if the count of supervisors is larger than 4 for.
Second question: select count(empid) and supervisor, use group by supervisor clause and add order by clause on the count column
I explained the logic, as for the actual sql code, you're gonna have to figure that out yourself.

Informix SQL merging/joining and updating a table

I have an Informix database.
EMPLOYEE
LAST_NAME FIRST_NAME SSN
----------------------------------------------
SMITH JOHN 123456789
DOE JANE 987654321
SMITH JOHN 5555555
SCHEDULE
SSN START END
---------------------------
123456789 8:00AM 4:00PM
987654321 8:00AM 4:00PM
I need to take the profile John Smith with ssn 5555555 and replace it with the other John Smith with ssn 123456789. Also with this query I need to update the Schedule table to update to with the new ssn 5555555.
The most important thing is that the profile with ssn 123456789 is now attached to the schedule table with the ssn 5555555. Then I need to be able to delete the old employee with ssn 123456789.
An original version of the question seemed to discuss truncating a 9-digit SSN into a 7-digit not-quite-SSN. The revised mapping of 123456789 ⟶ 5555555 is not one that can be done algorithmically. The original question also discussed dealing with multiple mappings, though the example data only shows and describes one.
I'm assuming that the Employee table does not yet have the 7-digit SSN for John Smith, even though the sample data shows that.
There are multiple ways to address the requirements. This is one way to do it.
To address the generalized mapping, we can define an appropriate temporary table and populate it with the relevant data:
CREATE TEMP TABLE SSN_map
(
SSN_9 INTEGER NOT NULL UNIQUE,
SSN_7 INTEGER NOT NULL UNIQUE
);
INSERT INTO SSN_map(SSN_9, SSN_7) VALUES(123456789, 5555555);
-- …
This table might need to be a 'regular' table if it will take time to get its content correct. This would allow multiple sessions to get the mapping done correctly.
If there is an algorithmic mapping between the 9-digit and 7-digit SSNs, you could still create the SSN_map table applying the algorithm to the SSN_9 (SSN) value to create the mapping. You might also be able to apply the algorithm 'on the fly' and avoid the mapping table, but it makes the UPDATE statement harder to get right.
Assuming the database supports transactions (Informix databases can be logged, meaning 'with transactions', or unlogged, meaning 'without transactions'), then:
BEGIN WORK;
-- Create 7-digit SSN entry corresponding to 9-digit SSN
INSERT INTO Employee(SSN, Last_Name, First_Name)
SELECT m.SSN_7, e.Last_Name, e.First_Name -- , other employee columns
FROM Employee AS e
JOIN SSN_map AS m ON e.SSN = m.SSN_9;
Some (older) versions of Informix won't let you SELECT from the table you are modifying as shown. If that's a problem, then use:
SELECT m.SSN_7, e.Last_Name, e.First_Name -- , other employee columns
FROM Employee AS e
JOIN SSN_map AS m ON e.SSN = m.SSN_9
INTO TEMP mapped_emp WITH NO LOG;
INSERT INTO Employee SELECT * FROM mapped_emp;
DROP TABLE mapped_emp;
Continuing: the Employee table now contains two entries for each mapped employee, one with the old 9-digit SSN, one with the new 7-digit not-quite-SSN.
UPDATE Schedule
SET SSN = (SELECT SSN_7 FROM SSN_map WHERE SSN_9 = SSN)
WHERE EXISTS(SELECT * FROM SSN_map WHERE SSN_9 = SSN);
This updates the Schedule with the new not-quite-SSN value. The WHERE EXISTS clause is there to ensure that only rows with an entry in the SSN mapping table are changed. If it was not present, any unmatched row would have the SSN set to NULL, which won't be good.
DELETE FROM Employee
WHERE SSN IN (SELECT SSN_9 FROM SSN_map);
COMMIT WORK;
This deletes the old data with 9-digit SSNs from the Employee table. You could drop the SSN_map table at this point too.
Complete test script
-- Outside the test, the Employee and Schedule tables would exist
-- and be fully loaded with data before running this script
BEGIN WORK;
CREATE TABLE EMPLOYEE
(
LAST_NAME CHAR(15) NOT NULL,
FIRST_NAME CHAR(15) NOT NULL,
SSN INTEGER NOT NULL PRIMARY KEY
);
INSERT INTO Employee(Last_Name, First_Name, SSN) VALUES('SMITH', 'JOHN', 123456789);
INSERT INTO Employee(Last_name, First_Name, SSN) VALUES('DOE', 'JANE', 987654321);
CREATE TABLE SCHEDULE
(
SSN INTEGER NOT NULL REFERENCES Employee,
START DATETIME HOUR TO MINUTE NOT NULL,
END DATETIME HOUR TO MINUTE NOT NULL,
PRIMARY KEY(SSN, START)
);
INSERT INTO Schedule(SSN, Start, End) VALUES(123456789, '08:00', '16:00');
INSERT INTO Schedule(SSN, Start, End) VALUES(987654321, '08:00', '16:00');
SELECT * FROM Employee;
SELECT * FROM Schedule;
-- Start the work for mapping SSN to not-quite-SSN
CREATE TEMP TABLE SSN_map
(
SSN_9 INTEGER NOT NULL UNIQUE,
SSN_7 INTEGER NOT NULL UNIQUE
);
INSERT INTO SSN_map(SSN_9, SSN_7) VALUES(123456789, 5555555);
-- In the production environment, this is where you'd start the transaction
--BEGIN WORK;
-- Create 7-digit SSN entry corresponding to 9-digit SSN
INSERT INTO Employee(SSN, Last_Name, First_Name)
SELECT m.SSN_7, e.Last_Name, e.First_Name -- , other employee columns
FROM Employee AS e
JOIN SSN_map AS m ON e.SSN = m.SSN_9;
UPDATE Schedule
SET SSN = (SELECT SSN_7 FROM SSN_map WHERE SSN_9 = SSN)
WHERE EXISTS(SELECT * FROM SSN_map WHERE SSN_9 = SSN);
DELETE FROM Employee
WHERE SSN IN (SELECT SSN_9 FROM SSN_map);
SELECT * FROM Employee;
SELECT * FROM Schedule;
-- When satisfied, you'd use COMMIT WORK instead of ROLLBACK WORK
ROLLBACK WORK;
--COMMIT WORK;
Sample output
The first four lines are the 'before' data; the last four are the 'after' data.
SMITH JOHN 123456789
DOE JANE 987654321
123456789 08:00 16:00
987654321 08:00 16:00
DOE JANE 987654321
SMITH JOHN 5555555
5555555 08:00 16:00
987654321 08:00 16:00
As you can see, the material for John Smith was updated correctly, but the material for Jane Doe was unchanged. This is correct given that there was no mapping for Jane.
For people not used to Informix: yes, you really can include DDL statements like CREATE TABLE inside a transaction, and yes, the created table really is rolled back if you roll back the transaction. Not all DBMS are as generous as that.

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.

sql check for logical errors in 2 columns

Assuming I have an employee table with 2 columns only:
employee_id
manager_id
All employees added to this table would have an accompanying manager_id that is actually an employee_id that already exists (save for one, the CEO probably doesn't have a manager, but that's not important).
If A is the manager of B, how do we enforce a check such that A's manager can take any value BUT B, thus resulting in a violation of the business rule?
I'd say the best way would be to create a TRIGGER on the insert into the table that would simply check that manager_id NOT IN (SELECT employee_id from employee where manager_id = %insertid%).
Half of the answer is a foreign key: manager_id references employee(employee_id)
The other half is a check constraint, manager_id<>employee_id
The problem goes deeper than that, you want to avoid any cycles in your graph, making it effectively a tree.
I think you're better off doing that at the application level.
UPDATE: But if you prefer to do it with a trigger, take a look at common table expressions (CTEs). You can create a recursive query in a trigger that checks for cycles:
create trigger prevent_management_cycles on employee
instead of update
as
declare #found_rows int
;with cycle_detector (employee_id) as (
select employee_id from inserted
union all
select employee.employee_id from employee
join cycle_detector
on employee.manager_id = cycle_detector.employee_id
)
select #found_rows = count(*)
from cycle_detector
join inserted
on inserted.manager_id = cycle_detector.employee_id
if #found_rows > 0
raiserror('cycle detected!', 1, 1)
else
-- carry on original update
update employee
set employee.manager_id = inserted.manager_id
-- other columns...
from employee
join inserted on employee.employee_id = inserted.employee_id
Note: it's assumed that employee_id is a primary key and manager_id is a foreign key pointing back to employee.employee_id.