I'm working with a SQL database for a Skills Matrix application and I need a query to return a list of employees that are qualified for a given position. Here's a brief overview of the relevant relationships:
Employee has many skills through
qualifications
Position has many
skills through requirements
Is there a efficient way to return a list of employees that have the qualifications to meet a certain position's requirements?
EDIT
employees
- id
- name
positions
- id
- title
skills
- id
- name
requirements
- position_id
- skill_id
qualifications
- employee_id
- skill_id
SELECT *
FROM employees e
WHERE NOT EXISTS (
SELECT *
FROM requirements r
WHERE r.position_id = 1234
AND NOT EXISTS (
SELECT *
FROM qualifications q
WHERE q.skill_id = r.skill_id
AND q.employee_id = e.employee_id
)
)
It will find all employees such that there are no requirement that is not filled by the employee's qualifications.
The use of nested NOT EXISTS is even mentioned in the MySQL Reference Maual
Sure, it's possible. I count seven tables there: Employee, Qualifation, EmployeeQualifications, Position, Requirement, PositionRequirements, and RequirementQualifications. If your Qualifications and Requirements will always be 1:1 (ie, they are the same thing), you can do it more simply (5 tables) like this: Employee, Position, Skill, EmployeeSkills, PositionSkills.
Once you have the tables defined, the trick to building the query is to first look for any positions for which an employee is not qualified, and then do an exclusion join with that result set back to the position table to get your results.
Yes, to achieve what you want you need, given a Position to return only those Employees, that have qualifications that collectively include all the skills the Position requires. If you retrieve these Employees, this list it will represent the list of the customers you are after.
Update
Since I do not have any experience with mysql, my reply only deals with sql server.
Try something like this:
select
qualifications.employee_id
from
qualifications
join
requirements
on
requirements.skill_id = qualifications.skill_id
where
requirements.position_id = 1234
group by
qualifications.employee_id
having
count(qualifications.skill_id) = (select count(distinct skill_id) from requirements where requirements.position_id = 1234)
Create an index on each of
employees.id
qualifications.employee_id
qualifications.skill_id
requirements.skill_id
requirements.position_id
Related
Assuming only one school can be identified in a specific town, you are required to move students from duplicated schools to originally created schools. We also assume that lowest school id implies the first schools to be created in
the database, hence original schools.
I want to achieve this simply by changing the school ids for duplicate school+town to the smallest school id (the original) in that category. This will take care of the student records table that is linked to this one via foreign key (school id).
How would I go about doing this on the table attached? I'm thinking along the lines of SELECT MIN, CASE STATEMENTS as well as GROUP BY and COUNT() but I'm at a loss on how to combine these. Anyone have an idea/code on how I would achieve the requirement above?
I'd assume that the school id is a unique identifier (key). Therefore you can't just update it in the schools table. You'd rather need to update the school_id column in the students table to point to the original school's id.
If this is the case you can do something along the lines of
-- get all students and their current school info for update
UPDATE students st JOIN schools sc
ON st.school_id = sc.id JOIN (
-- get ids of original schools
SELECT town, name, MIN(id) id
FROM schools
GROUP BY town, name
) q -- join students with a list original schools ids
ON sc.town = q.town AND sc.name = q.name
-- change the school id to the original one
SET st.school_id = q.id
-- but only for students that were associated with non-original schools
WHERE st.school_id <> q.id
Here is dbfiddle demo
I have three tables:
Department(ID,name) ,
SubDepartment(parentID,childID) and
Department_Employees(idEmployee, idDepartment)
In table "Department_Employees" idDepartment is foreign key linked
to ID of "Department" table.
In table "SubDepartment" parentID and childID are foreign keys both linked to ID of "Department" table.
One Department(parent) can have many SubDepartments(children) and those SubDepartments can have their own SubDepartments(children) but SubDepartment(child) can't be parent of any of his parent Departments.
"idEmployee" is distinct in table "Department_Employees" (Employee can belong to only one Department). These rules are covered from front-end side.
I have to count how many employees ("idEmployee") are in certain Department and ALL his SubDepartments. I also have to count how many SubDepartments are in certain Department including all children.
Lets say that I have something like this :
1. MainDepartment(6 employees)
- ITSector(3 employees)
* Databases(4 employees)
* Programmers(2 employees)
- DeliverySector(4 employees)
I need the output to be MainDepartment has 4 subDepartments and 19 employees.
Is it possible or advised to do this in one query with subqueries? I assume I need some kind of recursion for this but I don't know how to write it.
You can use nested set model to store the hierarchical data. That means that your tables would be of form:
Department(ID,name, parentID, left, right ) and Department_Employees(idEmployee, idDepartment).
SELECT COUNT(parent.name), COUNT(employee.id)
FROM Department AS node ,
Department AS parent,
Department_employee AS employee
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.idDepartment = product.idDepartment
AND parent.name = 'MAIN DEPARTMENT';
More on Nested set representation for hierarchical data
Other option is keep the database structure the same. You can use a sub queries or a stored procedure to find the sub departments.
select count(id), count(distinct(idDepartment)) from Department_employee
where idDepartment IN ( Getsubdepartment('Department') );
The recursive sounds like:
WITH RECURSIVE dep (main_id,child_id,name) as
(SELECT d.id,d.id,d.name
FROM department d
UNION ALL
SELECT d.main_id, sd.child, d.name || '.' || sd.name
FROM dep d INNER JOIN subdepartment sd on d.child_id=sd.parentid
)
SELECT s.name,count(idemployee)
FROM dep d INNER JOIN dep s ON d.name LIKE s.name || '%'
LEFT JOIN department_employees de ON d.child_id=de.iddepartment
GROUP BY s.name
ORDER BY s.name;
Tested on postgres it should work also in mysql.
It uses the path system for storage trees. It is very nice and easy. Left right does not apply very good with big and dinamic tree because at each insrt you have to update half db. If you make a copy paste of a branch you are going to update milions of records.
I am studying for SQL exam, and I came across this fact, regarding subqueries:
2. Main query and subquery can get data from different tables
When is a case when this feature would be useful? I find it difficult to imagine such a case.
Millions of situations call for finding information in different tables, it's the basis of relational data. Here's an example:
Find the emergency contact information for all students who are in a chemistry class:
SELECT Emergency_Name, Emergency_Phone
FROM tbl_StudentInfo
WHERE StudentID IN (SELECT b.StudentID
FROM tbl_ClassEnroll b
WHERE Subject = 'Chemistry')
SELECT * FROM tableA
WHERE id IN (SELECT id FROM tableB)
There is plenty of reasons why you have to get data from different tables, such as select sth from main query, which is based on subquery/subqueries from another tables. The usage is really huge.
choose customers from main query which is based on regions and their values
SELECT * FROM customers
WHERE country IN(SELECT name FROM country WHERE name LIKE '%land%')
choose products from main query which is greater or lower than average incoming salary of customers and so on...
You could do something like,
SELECT SUM(trans) as 'Transactions', branch.city as 'city'
FROM account
INNER JOIN branch
ON branch.bID = account.bID
GROUP BY branch.city
HAVING SUM(account.trans) < 0;
This would for a company to identify which branch makes the most profit and which branch is making a loss, it would help identify if the company had to make changes to their marketing approach in certain regions, in theory allowing for the company to become more dynamic and reactive to changes in the economy at any give time.
I'm far from being a database expert, so please feel free to let me know I'm doing it entirely wrong. I'm trying to create a set of tables that has the following basic rules:
There are companies, managers and employees. Managers can only belong to one company, but employees can belong to more then one manager. The table structure I've come up with is something like this:
# Companies
company_id
company_name
# Managers
manager_id
company_id
# Employees
employee_id
company_id
employee_name
# Managed By
employee_id
manager_id
Does this structure seem reasonable? I thought I need something like "Managed By" since an employee can have multiple managers.
What I'm boggling on is now how do I manage to grab the records I'd want. For example:
Get all employee names belonging to a certain manager
Get all employee names belonging to two certain managers
All employees to the same company
edit: I think I'm getting the queries figured out but this still feels unweidy, so any help on the table structure would be appreciated.
The fact I'm having problems writing queries for this information makes me think I've done something fundamentally wrong with the table structure. Hopefully someone here can set me right?
Generally, the scheme is correct. The one possibility that does exist, however, is that you could have data where an employee is managed by managers at more than two companies.
For your queries:
select distinct Employees.employee_name as name from Employees, Managers, ManagedBy where Managers.manager_id = X and ManagedBy.manager_id = Managers.manager_id and Employees.employee_id = ManagedBy.employee_id;
and
select distinct Employees.employee_name as name from Employees, Managers, ManagedBy where (Managers.manager_id = X or Managers.manager_id = Y) and ManagedBy.manager_id = Managers.manager_id and Employees.employee_id = ManagedBy.employee_id;
where X and Y are the manager IDs you want.
edit: I know you crossed out the queries you wanted, but the third one is this:
select distinct Employees.employee_name as name from Employees where Employees.company_id = Z;
where Z is the company ID you want.
All employees to the same company
SELECT employee_id, employee_name, company_name
FROM Employees
LEFT JOIN Companies ON Employees.company_id = Companies.company_id
WHERE Companies.company_id = ????
or if you pulling by name
WHERE Companies.company_name = 'ABC'
Get all employee names belonging to a certain manager
SELECT employee_id, employee_name, manager_id
FROM Employees
LEFT JOIN Managed_By ON Employees.employee_id = Managed_By.employee_id
WHERE Managed_By.manager_id = ????
manager_name in Managers table would be nice to have
I have a bunch of records (orders) that I want to make available to users to make reports from.
The users come from different departments, and I would like to make it, so each department can only see their own stuff.
I can't figure out how to do this the right way.
What I have now is:
- A model where I have placed a Filter on the Order table.
The filter can use GetUserID() to get the users name, but I can't figure out how I get from that to the "UserDepartment" table that maps users to specific departments.
Ofcourse, I would prefer a solution whereby I didn't have to create new access groups or edit the model for each department that someone might dream up.
Any clues?
(Using SQL server 2008)
EDIT: This link http://blogs.msdn.com/bobmeyers/articles/Implementing_Data_Security_in_a_Report_Model.aspx shows the basics of what I'm trying to do, but the author seems to assume that each record have a UserName field that can be matched.
In my case i want all users of department X to be able to access the line.
We had a similar problem to this and ended up writing a function in SQL.
The function did the following:
Received the username parameter from SRSS
Performed a lookup on the permissions table and retrieved the records (department Id's in your case).
returned the department Id's
Then our sql statement looked like this:
SELECT *
FROM ImportantData
WHERE DepartmentId IN (SELECT Id FROM fn_GetUserDepartmentAllocations(#UserName))
This did force us to modify all of the sql queries but it allowed us to do it with minimal complex logic.
The other thing that this allows for is if you have one user who transcends department boundaries: for example a manager of 2 departments.
CREATE FUNCTION [dbo].[fn_GetUserDepartmentAllocations]
(
#UserName NVARCHAR(100)
)
RETURNS
#TempPermissions TABLE
(
DepartmentId Int
)
AS
BEGIN
INSERT INTO #TempPermissions
SELECT DepartmentId
FROM DepartmentPermissions
WHERE DepartmentAllowedUsername = #UserName
RETURN
END
The main benefit to doing it this way is it also allows you to edit one place to change the entire permissions structure, you don't have to go through each and every report to change it, instead you change one place
For example you could have a manager who belongs to 2 departments but is not allowed to view them except on thursdays (I know silly example but you get the point hopefully).
Hope this helps
Pete
This assume that Users have Orders.
So, filter by users who exist in the same dept as the filter user. Don't filter orders directly.
I've guessed at schema and column names: hoep you get the idea...
SELECT
MY STuff
FROM
Order O
JOIN
UserDept UD ON O.UserCode = UD.UserCode
WHERE
EXISTS (SELECT *
FROM
UserDept UD2
WHERE
UD2.UserCode = #MYUSerCode
AND
UD2.DeptID = UD.DeptID)
--or
SELECT
MY STuff
FROM
Order O
JOIN
UserDept D ON O.UserCode = D.UserCode
JOIN
UserDept U ON D.DeptID = U.DeptID
WHERE
U.UserCode = #MYUSerCode
What you're trying to achieve is difficult using the GetUserID() method. To use that your source query would have to return a lot of redundant data, imagine something like the following:
/*
Table: User
Fields: UserID, LoginName, FullName
Table: Department
Fields: DepartmentID, Name
Table: UserDepartments
Fields: UserID, DepartmentID
Table: Order
Fields: OrderNumber, DepartmentID
*/
SELECT O.OrderNumber, O.DepartmentID, U.LoginName
FROM Order O
JOIN Department D ON D.DepartmentID = O.DepartmentID
JOIN UserDepartments UD ON UD.DepartmentID = D.DepartmentID
JOIN User U ON U.UserID = UD.UserID
This will give you more rows than you want, basically a copy of the order for each user in the department that owns the order.
Now you can apply your filter as described in the link you provided. This will filter it down to just one copy of the order rows for the current user if they're in the right department.
If this is a performance issue there's other alternatives, easiest being using a local report (.RDLC) in either ASP.NET, WinForms or WPF and passing user details off to the data call so the filtering can be done in the SQL.