Compute number of direct report for each employee in the organization (aggregation) - sql

FYI I use Redshift SQL.
I have a database that looks roughly like the one below (the database has multiple columns that I'll abstract away for simplicity).
This table is a representation of the hierarchical tree within my organization.
employee manager
-------- -------
daniel louis
matt martha
martha kim
laura matt
michael martha
...
As you can see, matt appears in two distinct records, one as the employee and the other as laura's manager. Martha appears in three records, one as an employee and in two other as manager.
I'd like to find a way to compute the number of direct reports each employee has. A conditional count in which the criteria would be where employee = manager, perhaps?
I guess I could find this information using a subquery and then join it back but I was wondering if there was a more "elegant" way to do this making use of window functions maybe.
The expected output for the table above would be:
employee manager direct_reports
-------- ------- --------------
daniel louis 0
matt martha 1
martha kim 2
laura matt 0
michael martha 0
...

I would approach this with a correlated subquery:
select
t.employee,
t.manager,
(select count(*) from mytable t1 where t1.manager = t.employee) direct_reports
from mytable t
This should be a quite efficient method, especially with an index on (employee, manager).

Use a left join and aggregation:
select em.employee, em.manager, count(ew.employee)
from employees em left join
employees ew
on ew.manager = em.employee
group by em.employee, em.manager;

Related

SQL Result to multiple array

MY SQL returns the following array...
id
staff
province
1
Ben
Ontario
2
Ben
Quebec
3
John
Manitoba
4
John
Saskatchewan
6
Kitty
Alberta
7
Kitty
Nova Scotia
I would like to have the record displayed like this...
staff
province
Ben
Ontario, Quebec
John
Quebec, Manitoba, Saskatchewan
Kitty
Alberta, Nova Scotia
what approach should I use to approach this?
Would be better to post the tables as well for clearer context.
You can use Aggregate functions and Grouping to help doing this. A GROUP BY to group the rows by staff column, then use GROUP_CONCAT() to concatenate province values in one string.
A reference of how you want it to be, unsure what table you are using or if there are any other factors but you can adapt as needed.
SELECT staff, GROUP_CONCAT(province SEPARATOR ', ') as province
FROM table_name
GROUP BY staff;

Cursor vs standard SQL

I have seen the following question approached to be solved as a cursor although I seen efficiency doing it as a multiple join. How can I solve this via a cursor and should i bother doing so as im extracting it via excel?
RecordID EmpID FirstName LastName HiredDate FiredDate
1111 1 John Flanagan 1/02/2013 1/02/2014
2222 1 Michael Richards 1/01/2014 1/02/2015
3333 3 Peter Johnson 1/08/2014 1/07/2016
4444 3 Jim Crow 1/09/2014 1/02/2017
5555 3 Own Wilson 1/010/2014 1/03/2015
Here's my attempt with SQL initially.
select EmpID, count(RecordID), HiredDate
from Employer
group by EmpID
order by DESC.
Need to write a query that returns each individual employer and for each row include the max employees that were hired by the company and the date of the last hire.
I was asked to write it as a cursor as they wanted to extract the above data line by line from excel.
If EmpId is employer id you are almost there:
SELECT EmpID, COUNT(RecordID), MAX(HiredDate)
FROM Employer
GROUP BY EmpID
ORDER BY EmpID ASC
This will get you the output you require, but I'm not sure what you wanted to order by...I've assumed EmpId.
I've also assumed SQL Server.

VBA Access SQL - field within LIKE operator

Can I use a table column within a Like operator? I've created an example,
TableA
Names Location
Albert Smith Senior Aberdeen
John Lee London
Michael Rogers Junior Newcastle
Mary Roberts Edinburgh
TableB
Names
Albert Smith
John Lee
Michael Rogers
I want to do a query such as:
SELECT TableA.Location
into NewTable
FROM TableA
WHERE TableA.Names Like '*[TableB.Names]*';
In this case, there would be no match for Mary Roberts, Edinburgh but the first three locations would be returned.
Is it possible to put a column into a like statement?
If not does anyone have any ideas how I could do this?
Hope you can help
PS I can't use an actual asterisk since this is removed and the text italicised, also I have read about using % instead but this has not worked for me.
You can join the two tables and use LIKE within the JOIN clause:
SELECT TableA.Location
into NewTable
FROM TableA
INNER JOIN TableB ON TableA.Names LIKE TableB.Names & '*';
Honestly, I had no idea that you can do this in Access before I tried it just now :-)

Easiest way to find IsManager in SQL

Simple structure table of employees
Employee Manager
Joe Smith Jon Smith
Jon Smith Pete Stevens
Pete Stevens NULL
Jared Scho Pete Stevens
....
Im just trying to return some results but I want an indicator on whether the person is a manager or not so the result should be:
Employee Manager IsAManager
Joe Smith Jon Smith 0
Jon Smith Pete Stevens 1
Pete Stevens NULL 1
Jared Scho Pete Stevens 0
The result set is showing that Joe Smith and Jared Scho are not managers...
So If I had a simple SQL Query
SELECT
Employee,
Manager,
As IsAManager --tried to do a case statement here....
FROM
Employee
I tried to do a case statement something to this effect:
SELECT CASE ISNULL(COUNT(*), 0) > 0 THEN 1 ELSE 0 END FROM Employee WHERE Manager = Employee
Not sure how to word it :)
Hopefully this is just a demo example not your real table structure.
SELECT Employee,
Manager,
CASE
WHEN EXISTS(SELECT *
FROM Employee e2
WHERE e2.Manager = e1.Employee) THEN 1
ELSE 0
END As IsAManager
FROM Employee e1
For details of how SQL Server processes EXISTS Subqueries in CASE Expressions see this article.
To determine whether an employee is a manager, you need to match the Employee's ID (in this case, the name) against the list of Manager IDs (in this case the Manager column). If you find a match, the employee is a manager. If you don't find a match, the employee is not a manager.
You can do this with a LEFT OUTER JOIN as shown here:
SELECT DISTINCT
E.Employee,
E.Manager,
CASE WHEN M.Employee IS NULL THEN 0 ELSE 1 END As IsAManager
FROM
Employee E LEFT OUTER JOIN Employee M
ON E.Employee = M.Manager
Please note the following:
You did not specify the SQL product you're using, so I tried to make the solution general. I'm guessing from your attempt to use ISNULL that it's SQL Server, but this solution should work in any product that supports CASE.
Your method of storing manager status has one problem, which is that you cannot represent a manager with no employees (you derive the manager status from the existence of the employee-manager relationship). If you want to be able to store manager status separately from the employee-manager relationship then CREATE TABLE Managers (Employee. . . PRIMARY KEY). This will make the code necessary to get back manager status a little easier to write as well.

Interesting SQL Sorting Issue

It's crunch time, deadline for my most recent contract is coming in two days and almost everything is complete and working fine (knock on wood) except for one issue.
In one of my stored procedures, I'm needing to return a result set as follows.
group_id name
-------- --------
A101 Craig
A102 Craig
Z101 Craig
Z102 Craig
A101 Jim
A102 Jim
Z101 Jim
Z102 Jim
B101 Andy
B102 Andy
Z101 Andy
Z102 Andy
The names need to be sorted by the first character of the group id and also include the Z101/Z102 entries. By sorting strictly by the group id, I get a result set as follows:
group_id name
-------- --------
A101 Craig
A102 Craig
A101 Jim
A102 Jim
B101 Andy
B102 Andy
Z101 Andy
Z102 Andy
Z101 Craig
Z102 Craig
Z101 Jim
Z102 Jim
I really can't think of a solution that doesn't involve me making a cursor and bloating the stored procedure up more than it already is. I'm sure a great mind out there has an elegant solution and I'm eager to see what the community can come up with.
Thanks a ton in advance.
Edit: Let me expand :) I'm sorry, it's late and I'm coffee addled.
The above result set is a special case for a special type of data entry. Being transparent, we're making an election based website and these are going to be candidates sorted by office, name, and then district.
Most offices have multiple districts in them except for district positions like magistrate/coroner, which will have only one. The Z comes in as the "district" for absentee machine and absentee paper votes.
The non-magistrate positions can be sorted by name first, as they are all grouped together. However, the existing system lists all magistrates in a huge clump of information, when they should be sorted by individual districts. This is where the issue lies.
To protect my pride, I want to add that I had no control over the normalization of the database. It was given to me by the client.
Here's the order clause of my stored procedure, if it helps:
ORDER BY candidate.party,
candidate.ballot_name,
CASE WHEN candidate.district_type = 'MAG' THEN LEFT(votecount.precinct_id, 1) END,
candidate.last_name,
candidate.first_name,
precinct.name
Edit 2: Here's where I currently stand (1:43 A.M.) -
I'm using a suggestion below to create a conditional inner join as follows:
IF candidate.district_type = 'MAG'
BEGIN
(
SELECT candidate.id AS candidate_id, candidate.last_name, LEFT(votecount.precinct_id, 1) AS district, votecount.precinct_id
FROM candidate
INNER JOIN votecount
ON votecount.candidate_id = candidate.id
GROUP BY name
) mag_order
INNER JOIN mag_order
ON mag_order.candidate_id = candidate.id
END
and then I'll sort it by mag_order.district, candidate.precinct_id, candidate.last_name.
For some reason I'm getting a SQL error when aliasing the ( SELECT ) as mag_order. Anyone see anything wrong with the code? I can't for the life of me. Sorry this is a bit tangential.
SELECT g1.group_id, g1.name
FROM
groups g1
INNER JOIN
(
SELECT MIN(group_id), name
FROM groups
GROUP BY name
) g2 on g1.name = g2.name
ORDER BY g2.group_id, g1.name, g1.group_id
ORDER BY name DESC, SUBSTR(group_id,1), group_id
SELECT groupId, name
FROM table
ORDER BY getFirstGroupId(name), name, groupId
Then your getFirstGroupId() function would return the first groupId for that name
SELECT MIN(groupId)
FROM groupTable
WHERE name = #name