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

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.

Related

What is the output of the query if the query try to fetch information form same table multiple time

Consider the following relational data table, employee. Now find the output for the following SQL statement?
SELECT COUNT(*)
FROM employee, employee, employee
Employee table
gid
name
Three
E101
John
HRM
E102
Lucy
Marketing
E103
Rick
Management
This will produce error since you didn't use unique aliases. You need to assign unique name (alias) to all the tables in from clause. But you should run it first. Here I am sharing a fiddle link please go there and run the query.
Schema (MySQL v5.7)
create table employee (gid varchar(20), name varchar(20),Three varchar(20));
insert into employee values('E101','John','HRM');
insert into employee values('E102','Lucy','Marketing');
insert into employee values('E103','Rick','Management');
Query #1
SELECT count(*) From employee a, employee b, employee c;
count(*)
27
View on DB Fiddle

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.

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

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 :-)

How to perform a mass SQL insert to one table with rows from two seperate tables

I need some T-SQL help. We have an application which tracks Training Requirements assigned to each employee (such as CPR, First Aid, etc.). There are certain minimum Training Requirements which all employees must be assigned and my HR department wants me to give them the ability to assign those minimum Training Requirements to all personnel with the click of a button. So I have created a table called TrainingRequirementsForAllEmployees which has the TrainingRequirementID's of those identified minimum TrainingRequirements.
I want to insert rows into table Employee_X_TrainingRequirements for every employee in the Employees table joined with every row from TrainingRequirementsForAllEmployees.
I will add abbreviated table schema for clarity.
First table is Employees:
EmployeeNumber PK char(6)
EmployeeName varchar(50)
Second Table is TrainingRequirementsForAllEmployees:
TrainingRequirementID PK int
Third table (the one I need to Insert Into) is Employee_X_TrainingRequirements:
TrainingRequirementID PK int
EmployeeNumber PK char(6)
I don't know what the Stored Procedure should look like to achieve the results I need. Thanks for any help.
cross join operator is suitable when cartesian product of two sets of data is needed. So in the body of your stored procedure you should have something like:
insert into Employee_X_TrainingRequirements (TrainingRequirementID, EmployeeNumber)
select r.TrainingRequirementID, e.EmployeeNumber
from Employees e
cross join TrainingRequirementsForAllEmployees r
where not exists (
select 1 from Employee_X_TrainingRequirements
where TrainingRequirementID = r.TrainingRequirementID
and EmployeeNumber = e.EmployeeNumber
)

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.