How to create views against a hierarchical table - sql

I am learning how to create view using SQL server. I am trying to add a view called Managers in the Northwind database that shows only employees that supervise other employees. This is what I have so far.
Create View Manager_vw
As
Select LastName,
FirstName,
EmployeeID
From Employees
Where
What I am stuck on is how and I going to put in supervise other employees. I am not to sure how to do this. If someone can help me understand how to do this.

In northwind.dbo.employees you would find employees who supervise other employees by looking the reportsto column. Basically you want to return employees whose id is in the reportsto column in another row. That can be done like this:
SELECT LastName,
FirstName,
EmployeeID
FROM employees E
WHERE EXISTS(SELECT * FROM Employees WHERE reportsTo = E.EmployeeID)
The EXISTS is like a JOIN but is usually implemented as a "semi-join" which will stop processing after it finds a singe match (rather than finding all the subordinate employees which would take extra work) Because it doesn't return any additional records, you also save the cost of the additional step to eliminate duplicates (a JOIN would do more work to process the join, and even more work to undo the work that wasn't necessary by doing a DISTINCT.)
You'll notice that I reference E.EmployeeID in the subquery, which relates the subquery to the outer query, this is called a Correlated Subquery.
A word of caution: Views have their place in a DB but can easily be misused. When an engineer comes to the database from an OO background, views seem like a convenient way to promote inheritance and reusability of code. Often people eventually find themselves in a position where they have nested views joined to nested views of nested views. SQL processes nested views by essentially taking the definition of each individual view and expanding that into a beast of a query that will make your DBA cry.
Also, you followed excellent practice in your example and I encourage you to continue this. You specified all your columns individually, never ever use SELECT * to specify the results of your view. It will, eventually, ruin your day. You'll see I do have a SELECT * in my EXISTS clause but EXISTS does not return a resultset and the optimizer will ignore that in that specific case.

Here's another option:
SELECT DISTINCT manager_tbl.*
FROM Employees AS staff_tbl
JOIN Employees AS manager_tbl
ON staff_tbl.ReportsTo = manager_tbl.EmployeeID
Adapted from this site. There are a number of example queries there that you might find interesting and useful.
Notes:
Using the DISTINCT keyword because a single manager could have more than one direct report. DISTINCT will omit the repetition caused by such a one-to-many relationship.
The Employees table in the Northwind database is an example of a hierarchical relationship modeled in a single table.
All together:
CREATE VIEW Manager_vw
AS
SELECT DISTINCT manager_tbl.*
FROM Employees AS staff_tbl
JOIN Employees AS manager_tbl
ON staff_tbl.ReportsTo = manager_tbl.EmployeeID

Related

Populate SQL query with blank rows

(This is a general SQL question, but I am specifically using MSAccess 2010 so looking for how to do this with Access' flavor of SQL)
I have a table called offices which has id, office_name, num_desks.
Another table called employees which has id, employee_name.
And a final table called employee_offices which has id, office_id, employee_id.
I can assign employees to offices via employee_offices.
I am trying to generate a report which shows all offices and the employees assigned to the, but also includes blank lines for any empty desks in that office.
I realize a "simple" way to do this would be to create a desks table with id, office_id, delete the num_desks column from the offices table and change employee_offices to something like employee_desks. Then my report would be a simple LEFT OUTER JOIN and it would include all the unassigned desks. However for the sake of sanity (in this case, there is no contextual difference between desks), I am not going to do this. Plus if I start deleting desks I have referential constraints to deal with (which obviously exist for a good reason and would catch the fact that I am leaving employees without a desk), but I just want to be able to change the number of desks.
I can calculate the number of empty desks (or lack of desks) through the following command:
SELECT
office_id,
num_desks - num_employees AS desk_diff,
MAX(0, num_desks - num_employees) AS blank_rows_to_add
FROM offices LEFT OUTER JOIN (
SELECT office_id, COUNT(employee_id) AS num_employees
FROM employee_offices
GROUP BY office_Id
) AS num_employees_by_office ON offices.id = num_employees_by_office.office_id
Is there a way to take this number (blank_rows_to_add) and somehow utilze it to add that many blank rows (or at least the row only has the office_id/office_name) to a report showing a list of employees by office? I know this can be done with VBA but I am specifically looking for an SQL method that also doesn't include a temp table if at all possible.
Thank you.

Employee table - hierarchy with some caveats

I've got an issue with a sort of standard employee table. In my example I have done self joins to the source however I don't get the end structure that I would like.
The hierarchy is a few levels up.
In a simple form the code is like:
select e.employee_name
, e.teamlead_name
, e1.teamlead_name 'manager_name'
, e2.teamlead_name 'senior_manager_name'
, e3.teamlead_name 'director_name'
from employees e
left join employees e1 on
e.teamlead_id = e1.employee_id
left join employees e2 on
e1.teamlead_id = e2.employee_id
left join employees e3 on
e2.teamlead_id = e3.employee_id
This gives me a good base but the director for example will become a teamlead when one of their direct reports is listed as a employee. Also get the same issue but for managers and senior managers as well where they won't be at level you would expect. This wouldn't be an issue except for reporting when the hierarchy isn't what the end users expect as the director, senior managers and managers fall into all the levels on the hierarchy.
I would prefer in this instance for the data to be structured as below.
Currently is like this
employee_name director_name null null null
Would like this
employee_name null null null director_name
I've kind of found a way around the issue by using case statements however isn't exactly the cleanest way of doing it and I'm hoping there is a nicer way of doing it.
To get the director aligned properly it's easier as there is nobody above them so I have done something like the below which works and always aligns the director to the correct spot then I separately use a nullif when to null out the other columns this is done with a CTE.
concat (e3.teamlead_name,
case when e1.teamlead_name is null
and e2.teamlead_name is null
and e3.teamlead_name is null
then e.teamlead_name end,
case when e2.teamlead_name is null
and e3.teamlead_name is null
then e1.teamlead_name end,
case when e3.teamlead_name is null
then e2.teamlead_name end) 'director_name'
The director isn't an employee in this system, they only exist a teamleader and above.
I think I could probably resolve and eventually get everything aligned as they want it doing it as per the above but surely there is a much better way of doing this? Any guidance would be appreciated. Thanks
So I would write this as three CTEs (Common Table Expressions) to get emp_Level+1, emp_level+2 and emp_level+3 and I would include the management level descriptor in the CTE.
I would then UNION together the three CTEs to provide a combined data set with up to 3 records per employee and then create a dynamic pivot table based on the management level descriptions. You should be able to Google all of these processes, but come back with your code if you get stuck.
It really depends on what you are going to do with the data once you have it. It may make more sense to let the reporting tool do the pivoting, especially if you are going to use SSRS or Excel

Ms-Access: counting from 2 tables

I have two tables in a Database
and
I need to retrieve the number of staff per manager in the following format
I've been trying to adapt an answer to another question
SELECT bankNo AS "Bank Number",
COUNT (*) AS "Total Branches"
FROM BankBranch
GROUP BY bankNo
As
SELECT COUNT (*) AS StaffCount ,
Employee.Name AS Name
FROM Employee, Stafflink
GROUP BY Name
As I look at the Group BY I'm thinking I should be grouping by The ManID in the Stafflink Table.
My output with this query looks like this
So it is counting correctly but as you can see it's far off the output I need to get.
Any advice would be appreciated.
You need to join the Employee and Stafflink tables. It appears that your FROM clause should look like this:
FROM Employee INNER JOIN StaffLink ON Employee.ID = StaffLink.ManID
You have to join the Eployee table twice to get the summary of employees under manager
select count(*) as StaffCount,Manager.Name
from Employee join Stafflink on employee.Id = StaffLink.EmpId
join Employee as Manager on StaffLink.ManId = Manager.Id
Group by Manager.Name
The answers that advise you on how to join are correct, assuming that you want to learn how to use SQL in MS Access. But there is a way to accomplish the same thing using the ACCESS GUI for designing queries, and this involves a shorter learning curve than learning SQL.
The key to using the GUI when more than one table is involved is to realize that you have to define the relationships between tables in the relationship manager. Once you do that, designing the query you are after is a piece of cake, just point and click.
The tricky thing in your case is that there are two relationships between the two tables. One relationship links EmpId to ID and the other links ManId to ID.
If, however, you want to learn SQL, then this shortcut will be a digression.
If you don't specify a join between the tables, a so called Cartesian product will be built, i.e., each record from one table will be paired with every record from the other table. If you have 7 records in one table and 10 in the other you will get 70 pairs (i.e. rows) before grouping. This explains why you are getting a count of 7 per manager name.
Besides joining the tables, I would suggest you to group on the manager id instead of the manager name. The manager id is known to be unique per manager, but not the name. This then requires you to either group on the name in addition, because the name is in the select list or to apply an aggregate function on the name. Each additional grouping slows down the query; therefore I prefer the aggregate function.
SELECT
COUNT(*) AS StaffCount,
FIRST(Manager.Name) AS ManagerName
FROM
Stafflink
INNER JOIN Employee AS Manager
ON StaffLink.ManId = Manager.Id
GROUP BY
StaffLink.ManId
I don't know if it makes a performance difference, but I prefer to group on StaffLink.ManId than on Employee.Id, since StaffLink is the main table here and Employee is just used as lookup table in this query.

sql resultset order defined?

Say I've employee table where any employee can be related to any other employee (many to many). Each employee has many characteristics that are stored separately.
emp: id,name
related: name,emp1role,emp1id,emp2role,emp2id
chars: empid,name,value
I want to get all the characteristics of employees who are related via 'xxx' along with the relation. I am currently using this query:
SELECT c.empid, c.Name, c.Value
FROM chars as c, related as r
WHERE r.name='xxx' AND (r.emp1id=c.empid OR r.emp2id=c.empid)
This works and it gives related employees one after another i.e. if emp22 & emp43 are related via 'xxx' then I am getting chars of emp43 followed by emp22 and so on. This way I am able to know which two employees are related (which is needed). However, I want to know if this order is mere luck or is it well-defined. This is in SQLite.
If it is not defined way, how else can I do it? Also, I need to know their respective roles. I want to preferably do it in one query. Can you think of some other query?
Thanks in advance,
Manish
PS: These are not actual tables. They are here for simplicity of asking question.
In SQL, ordering of the result is undefined unless you have an explicit ORDER BY clause in your query. So I believe you want:
SELECT c.empid, c.Name, c.Value
FROM chars as c, related as r
WHERE r.name='xxx' AND (r.emp1id=c.empid OR r.emp2id=c.empid)
ORDER BY c.empid ASC
I tend to add a unique field (such as a primary key) at the end, to if not get an obvious ordering when there are multiple records matching in all other order fields, at least get deterministic ordering. But that's largely a matter of style and choice; it's by no means required.

How to design the database schema to link two tables via lots of other tables

Although I'm using Rails, this question is more about database design. I have several entities in my database, with the schema a bit like this: http://fishwebby.posterous.com/40423840
If I want to get a list of people and order it by surname, that's no problem. However, if I want to get a list of people, ordered by surname, enrolled in a particular group, I have to use an SQL statement that includes several joins across four tables, something like this:
SELECT group_enrolment.*, person.*
FROM person INNER JOIN member ON person.id = member.person_id
INNER JOIN enrolment ON member.id = enrolment.member_id
INNER JOIN group_enrolment ON enrolment.id = group_enrolment.enrolment_id
WHERE group_enrolment.id = 123
ORDER BY person.surname;
Although this works, it strikes me as a bit inefficient, and potentially as my schema grows, these queries could get more and more complicated.
Another option could be to join the person table to all the other tables in the query by including person_id in the other tables, then it would just be one single join, for example
SELECT group_enrolment.*, person.*
FROM person INNER JOIN group_enrolment ON group_enrolment.person_id
WHERE group_enrolment.id = 123
ORDER BY person.surname;
But this would mean that in my schema, the person table is joined to a lot of other tables. Aside from a complicated schema diagram, does anyone see any disadvantages to this?
I'd be very grateful for any comments on this - whether what I'm doing now (the many table join) or the second solution or another one that hasn't occurred to me is the best way to go.
Many thanks in advance
Well, joins are what databases do. Having said that, you may consider propagating natural keys in your model, which would then allow you to skip over some tables in joins. Take a look at this example.
EDIT
I'm not saying that this will match your model (problem), but just for fun try similar queries on something like this: