SQL hierarchyid type issue - sql

The full task sounds like this: Display the content of employees whose immediate supervisor is younger and less employed in the company.
Columns:
Manager Name | Date of hiring a manager | Head's date of birth
Employee name | Employee hiring date | Employee's date of birth
I already broke my head here:
SELECT
LastName + ' ' + FirstName AS SupervisorFullName,
HireDate,
BirthDate,
(SELECT LastName + ' ' + FirstName
FROM HumanResources.Employee AS subHrE
WHERE HrE.OrganizationNode.IsDescendantOf(subHrE.OrganizationNode) = 1
AND HrE.OrganizationLevel = HrE.OrganizationNode.GetLevel() + 1) AS EmployeeFullName,
(SELECT HireDate
FROM HumanResources.Employee AS subHrE
WHERE HrE.OrganizationNode.IsDescendantOf(subHrE.OrganizationNode) = 1
AND HrE.OrganizationLevel = HrE.OrganizationNode.GetLevel() + 1) AS HireDateEmp,
(SELECT BirthDate
FROM HumanResources.Employee AS subHrE
WHERE HrE.OrganizationNode.IsDescendantOf(subHrE.OrganizationNode) = 1
AND HrE.OrganizationLevel = HrE.OrganizationNode.GetLevel() + 1) AS BithDateEmp
FROM
HumanResources.Employee as HrE
JOIN
Person.Person as P ON HrE.BusinessEntityID = P.BusinessEntityID
ORDER BY
SupervisorFullName ASC
Output
AdventureWork2016 db is used for work
Full Schema AW2016

There's definitely a certain way to think about use of hierarchyid that, once you get the hang of it, opens a lot of possibilities. Here's what I came up with:
WITH FullPerson AS (
SELECT CONCAT_WS(' ', p.FirstName, p.MiddleName, p.LastName) AS [FullName],
e.HireDate,
e.BirthDate,
e.OrganizationNode
FROM HumanResources.Employee AS e
JOIN Person.Person AS p
ON p.BusinessEntityID = e.BusinessEntityID
)
SELECT
manager.OrganizationNode.ToString(),
manager.FullName,
manager.HireDate,
manager.BirthDate,
subordinate.OrganizationNode.ToString(),
subordinate.FullName,
subordinate.HireDate,
subordinate.BirthDate
FROM FullPerson AS subordinate
JOIN FullPerson AS manager
ON subordinate.OrganizationNode.GetAncestor(1) = manager.OrganizationNode
WHERE manager.HireDate > subordinate.HireDate
AND manager.BirthDate > subordinate.BirthDate;
Breaking it down, I create a common table expression to join Employee and Person as I'll need columns from both tables for both subordinates and their managers. The real trick is the join condition. subordinate.OrganizationNode.GetAncestor(1) = manager.OrganizationNode says "take the subordinate's OrganizationNode and go one level up the tree". What's amazing to me is that this sort of query can be supported by indexes and indeed there is an index on that column in the AdventureWorks schema! In addition to the columns you asked for, I added a human-readable representation of OrganizationNode to help with the visualization of how the data relates.

Related

SQL: How to concatenate two cells from one column of multiple rows if all of the other row's cells are equal

Looking for DepartmentName1 + ', ' + DepartmentName2
I'm trying to merge two rows into one row when only one column has different values. Specifically I'm trying to list the name, job title, gender, pay rate, hire date and department name of the top 100 highest paid employees of the AdventureWorks2017 database. Here is the code I have so far:
SELECT TOP 100 (P.FirstName + ' ' + P.LastName) AS Name, HRE.JobTitle, HRE.Gender,
CAST(HRPH.Rate AS Decimal(10,2)) AS PayRate, HRE.HireDate, HRD.Name AS Department
FROM ((((Person.Person AS P
INNER JOIN HumanResources.Employee AS HRE
ON P.BusinessEntityID = HRE.BusinessEntityID)
INNER JOIN
(SELECT BusinessEntityID, MAX(RateChangeDate) AS RCD, MAX(Rate) AS Rate
FROM HumanResources.EmployeePayHistory
GROUP BY BusinessEntityID) AS HRPH
ON HRE.BusinessEntityID = HRPH.BusinessEntityID)
INNER JOIN HumanResources.EmployeeDepartmentHistory AS HRDH
ON HRE.BusinessEntityID = HRDH.BusinessEntityID)
INNER JOIN HumanResources.Department AS HRD
ON HRDH.DepartmentID = HRD.DepartmentID)
ORDER BY HRPH.Rate DESC;
This gives me the following result:
Two questions:
How can I get every 'Name' to be listed only once, regardless of DepartmentName? For example: Rows 5 & 6 to be only Row 5: Laura Norman | Chief Financial Officer | F | 60.10 | 2009-01-31 | Executive, Finance.
OR, David Bradley...|...Marketing, Purchasing
Does my code include an employee that may have gotten a pay cut? Meaning, the RateChangeDate (RCD) is MAX but the Rate is not?
Using Microsoft SQL Server 2019
I bet you can make use of the string_agg() to aggregate the values with a delimiter in a query field.
SELECT TOP 100 (P.FirstName + ' ' + P.LastName) AS Name, HRE.JobTitle, HRE.Gender,
CAST(HRPH.Rate AS Decimal(10,2)) AS PayRate, HRE.HireDate, STRING_AGG(HRD.Name,',') AS Department
FROM ((((Person.Person AS P
INNER JOIN HumanResources.Employee AS HRE
ON P.BusinessEntityID = HRE.BusinessEntityID)
INNER JOIN
(SELECT BusinessEntityID, MAX(RateChangeDate) AS RCD, MAX(Rate) AS Rate
FROM HumanResources.EmployeePayHistory
GROUP BY BusinessEntityID) AS HRPH
ON HRE.BusinessEntityID = HRPH.BusinessEntityID)
INNER JOIN HumanResources.EmployeeDepartmentHistory AS HRDH
ON HRE.BusinessEntityID = HRDH.BusinessEntityID)
INNER JOIN HumanResources.Department AS HRD
ON HRDH.DepartmentID = HRD.DepartmentID)
GROUP BY P.FirstName,P.LastName,HRE.JobTitle, HRE.Gender, HRPH.Rate, HRE.HireDate
ORDER BY HRPH.Rate DESC;
To answer the second part, I took the liberty of creating an example and you may be able to work into your solution. The data you are working with lacks a unique key and using FirstName, LastName, and Gender is an obviously bad candidate for a unique key. You also mention RateChangeDate but do not mention how to handle that value when the data aggregates. The query below basically ignores RateChangeDate on the output and marks the records that have a decrease in pay. Another query into the data is needed to remove those records, below I did it using a HAVING clause.
DECLARE #X TABLE (ID INT, Rate MONEY, RateChangeDate DATETIME, Department NVARCHAR(50))
INSERT #X VALUES
(1,25.00,'01/01/2021','A'),
(1,23.00,'05/01/2021','A'),
(2,25.00,'01/01/2021','A'),
(3,25.00,'01/01/2021','A'),
(3,26.00,'02/01/2021','A'),
(4,25.00,'01/01/2021','A'),
(4,25.00,'01/01/2021','B')
SELECT
ID,
SUM(LatestRate) AS LatestRate,
MAX(MaxRateChange) AS RateChanges,
Departments
FROM
(
SELECT
ID,
STRING_AGG(Department,',') AS Departments,
Rate,
MAX(RateChangeDate) AS MaxRateChange,
CASE WHEN LAG(Rate) OVER (PARTITION BY ID ORDER BY RateChangeDate) > Rate THEN 1 ELSE 0 END AS DecreaseInPay,
CASE WHEN MAX(RateChangeDate)OVER(PARTITION BY ID) = RateChangeDate THEN Rate ELSE NULL END LatestRate
FROM
#X
GROUP BY
ID,Rate,RateChangeDate
)AS X
GROUP BY
ID,Departments
HAVING
MAX(DecreaseInPay) = 0

How do I write a subquery to concatenate Last name and First name using another table in database in SQL Server?

I'm using 2 tables, Orders and Employees. Both have IDs of employees. I need to get a total amount of orders completed by each separate employee.
I want my result to be this: full name, total orders.
What I could get is: employee ID, total orders.
I can't connect Order.EmployeeID with Employees.EmployeeID, even when using CAST or CONVERT.
I can make separate queries to concat names, and to show orders by employee ID, but I can't wrap my mind how to make a subquery to give me full name of each employee and total amount of their orders.
SELECT
[dbo].[Orders].EmployeeID AS Seller,
COUNT(OrderID) AS Amount
FROM
[dbo].[Orders]
JOIN
[dbo].[Employees] ON [dbo].[Employees].EmployeeID = [dbo].[Orders].EmployeeID
GROUP BY
[dbo].[Orders].EmployeeID;
I expect the following result:
|Name |TotalOrders|
+------------+-----------+
|Johnny Bravo| 120 |
Current result however is:
|ID|TotalOrders|
+--+-----------+
|1 |120 |
Simply concatenate them, like this for example
SELECT
o.EmployeeID AS Seller,
CONCAT(e.FirstName + ' ', e.LastName) AS FullName,
COUNT(o.OrderID) AS Amount
FROM
[dbo].[Orders] o
JOIN
[dbo].[Employees] e ON o.EmployeeID = e.EmployeeID
GROUP BY
o.EmployeeID, e.FirstName, e.LastName
You could also do this
(e.FirstName + ' ' + e.LastName) as FullName
But beware of this, when one of the columns is NULL then the result will also be NULL
Then you would have to do this
(isnull(e.FirstName, '') + ' ' + isnull(e.LastName, '')) as FullName
Therefore, the CONCAT function is easier, it will convert NULL values into empty strings for you.
See also this Documentation
EDIT
As noted by Sean Lange, it is better to do
Concat(e.FirstName + ' ', e.LastName) as FullName,
in stead of
Concat(e.FirstName, ' ', e.LastName) as FullName,
Because this way you will have no leading spaces when the firstname is null
SELECT [dbo].[Orders].EmployeeID AS Seller,Concat ([Orders].FirstName,[Orders].Lastname) fullname,
COUNT(OrderID) AS Amount
FROM [dbo].[Orders]
JOIN [dbo].[Employees] ON [dbo].[Employees].EmployeeID = [dbo].[Orders].EmployeeID
GROUP BY [dbo].[Orders].EmployeeID,[Orders].FirstName,[Orders].Lastname;

Sql Query! Can someone explain this?

Q1 : List employees who have the biggest salary in their departments
Q2 : List employees that don't have a boss in the same department
Q1 Answer)
Select e.name, Max_Sal.Sal
From Employees e
JOIN (Select d.DepartmentID,max(e.Salary) As Sal
From Employees e
JOIN Departments d ON e.DepartmentId=d.DepartmentId
Group by d.departmentID
) As Max_Sal
ON e.DepartmentId=Max_Sal.DepartmentID
and e.Salary=Max_Sal.Sal;
In Q2, I don't understand the question, [don't have a boss in the same department]. How to know boss is in which department?
Q2 : List employees that don't have a boss in the same department
There is a slight trick to this question.
1 - Join
SELECT e.*
FROM Employees e
LEFT OUTER JOIN Employees b on e.BossId = b.EmployeeId
Notice the following:
We've used the Employees table twice, but I've given them separate names. e is every employee; and b is that employee's boss.
We use a LEFT OUTER JOIN to account for the possibility that an employee does not have a boss (b will be NULL) If we had used an INNER JOIN, the employees with no boss would have been omitted from the result.
Now, we have a table of every employee, connected to their boss. But we still need to filter the list as requested.
2 - Filter
There are three possible states that an employee can be in:
They have no boss.
They have a boss, who works in the same department.
They have a boss, who works in a different department.
It's important to realize that the requested output needs to contain both the employees in situation 3 and the employees in situation 1. We should only omit the employees in situation 2.
Using pseudo-SQL, this means your query should look like this:
SELECT *
FROM the_data
WHERE
situation_1_applies
OR
situation_3_applies
The individual filters are not that hard to write:
SELECT e.*
FROM Employees e
LEFT OUTER JOIN Employees b on e.BossId = b.EmployeeId
WHERE
-- situation_1_applies
-- When b is NULL, all of b's columns are NULL
b.DepartmentId is NULL
OR
-- situation_3_applies
b.DepartmentId <> e.DepartmentId
Don't have a boss in the same department, so you can think bossID in Employees table is null. So you can write an SQL query for a group all of the people have the same department who have the bossID is null :), So it not real because employees have to have a boss (not null), If you show me full field of table, maybe I will show you exactly my answer.
In the other hand, you can think, They have the same bossID, so your mission will write an SQL query for a group all of the people have the same bossID but they have a different department.
I have an example MySQL query (for same bossID but different department):
SELECT * FROM Employees as e
INNER JOIN department d ON d.departmentId = e.deparmentId
WHERE e.DepartmentID in (
SELECT DepartmentID
FROM Employees
GROUP BY DepartmentID
HAVING COUNT(*) = 1 /* it's fixed */
) AND e.BossID = 2 /* any value of bossID you want, you can join it with Boss table */
===== Update my answer =====
I have an idea for the question.
Anyone must have a boss, and boss also is an employee in table SQL.
' employeeName ' department name ' boss name
--------------------------------------------------------
' King ' Director '
' Victor ' Marketing ' King
' Angela ' Product ' King
' Ken ' Marketing ' Victor
' LaLa ' Marketing ' Victor
' Jery ' Business ' Ken
And because don't have a boss in the same department -> their department can't have their boss. => the result as the following table:
' employeeName ' department name ' boss name
--------------------------------------------------------
' King ' Director '
' Victor ' Marketing ' King
' Angela ' Product ' King
' Jery ' Business ' Ken
If you have a right way to understand this question, you can post here for everyone can understand. Thanks

SQL Query - Unsure How to Fix Logical Error

Edit: Sorry! I am using Microsoft SQL Server.
For clarification, you can have a department named "x" with a list of jobs, a department named "y" with a different list of jobs, etc.
I also need to use >= ALL instead of TOP 1 or MAX because I need it to return more than one value if necessary (if job1 has 20 employees, job2 has 20 employees and they are both the biggest values, they should both return).
In my query I'm trying to find the most common jobTitle and the number of employees that work under this jobTitle, which is under the department 'Research and Development'. The query I've written consists of joins to be able to return the necessary data.
The problem I am having is with the WHERE statement. The HAVING COUNT(JobTitle) >= ALL is finding the biggest number of employees that work under a job, however the problem is that my WHERE statement is saying the Department must be 'Research and Development', but the job with the most amount of employees comes from a different department, and thus the output produces only the column names and nothing else.
I want to redo the query so that it returns the job with the largest amount of employees that comes from the Research and Development department.
I know this is probably pretty simple, I'm a noob :3 Thanks a lot for the help!
SELECT JobTitle, COUNT(JobTitle) AS JobTitleCount, Department
FROM HumanResources.Employee AS EMP JOIN
HumanResources.EmployeeDepartmentHistory AS HIST
ON EMP.BusinessEntityID = HIST.BusinessEntityID JOIN
HumanResources.Department AS DEPT
ON HIST.DepartmentID = DEPT.DepartmentID
WHERE Department = 'Research and Development'
GROUP BY JobTitle, Department
HAVING COUNT(JobTitle) >= ALL (
SELECT COUNT(JobTitle) FROM HumanResources.Employee
GROUP BY JobTitle
)
If you only want one row, then a typical method is:
SELECT JobTitle, COUNT(*) AS JobTitleCount
FROM HumanResources.Employee AS EMP JOIN
HumanResources.EmployeeDepartmentHistory AS HIST
ON EMP.BusinessEntityID = HIST.BusinessEntityID JOIN
HumanResources.Department AS DEPT
ON HIST.DepartmentID = DEPT.DepartmentID
WHERE Department = 'Research and Development'
GROUP BY JobTitle
ORDER BY COUNT(*) DESC
FETCH FIRST 1 ROW ONLY;
Although FETCH FIRST 1 ROW ONLY is the ANSI standard, some databases spell it LIMIT or even SELECT TOP (1).
Note that I removed DEPARTMENT both from the SELECT and the GROUP BY. It seems redundant.
And, if I had to guess, your query is going to overstate results because of the history table. If this is the case, ask another question, with sample data and desired results.
EDIT:
In SQL Server, I would recommend using window functions. To get the one top job title:
SELECT JobTitle, JobTitleCount
FROM (SELECT JobTitle, COUNT(*) AS JobTitleCount,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM HumanResources.Employee AS EMP JOIN
HumanResources.EmployeeDepartmentHistory AS HIST
ON EMP.BusinessEntityID = HIST.BusinessEntityID JOIN
HumanResources.Department AS DEPT
ON HIST.DepartmentID = DEPT.DepartmentID
WHERE Department = 'Research and Development'
GROUP BY JobTitle
) j
WHERE seqnum = 1;
To get all such titles, when there are duplicates, use RANK() or DENSE_RANK() instead of ROW_NUMBER().
with employee_counts as (
select
hist.DepartmentID, emp.JobTitle, count(*) as cnt,
case when dept.Department = 'Research and Development' then 1 else 0 end as is_rd,
from HumanResources.Employee as emp
inner join HumanResources.EmployeeDepartmentHistory as hist
on hist.BusinessEntityID = emp.BusinessEntityID
inner join HumanResources.Department as dept
on dept.DepartmentID = hist.DepartmentID
group by
hist.DepartmentID, emp.JobTitle
)
select * from employee_counts
where is_rd = 1 and cnt = (
select max(cnt) from employee_counts
/* where is_rd = 1 */ -- ??
);

Creating new columns from data in other columns based on primary key

7.1 List the employee name, employee title, manager name, and manager title for each employee.
Use clear column aliases for your results, and do not exclude any employees in your list.
I'm new to SQL so bear with me. I've managed to have the output list of every employee, their job title, and the employee ID of the employee of they report to (aka their manager). I can't for the life of me figure out how to properly add two additional columns (ManagerName and ManagerJobTitle) with the necessary correct information in it.
Here is my query so far.
Select (FirstName + ' ' + LastName) as EmployeeName, Title as JobTitle, ReportsTo
From Employees E
Order By ReportsTo
You didn't show any schema for your tables, but from your query I can see you are missing the join with the manager table, from where those two columns where probably supposed to come.
Edit: OK, now I get it. The employee table relates to itself. You didn't specified the PK for the table, so I'll assume the Employee has an Id. If the key is another column, just change it in the query below:
Select (E FirstName + ' ' + E.LastName) as EmployeeName,
E.Title as JobTitle,
(R.FirstName + ' ' + R.LastName) as 'ManagerName',
R.Title as 'ManagerTitle'
From Employees E
LEFT JOIN
Employees R ON E.ReportsTo = R.Id
Order By ReportsTo
Edit 2: Changed from INNER JOIN to LEFT JOIN, as a employee can have no manager.
Select (E.FirstName + ' ' + E.LastName) as EmployeeName, E.Title as JobTitle, mgr.ReportsTo, (mgr.FirstName + ' ' + mgr.LastName) as ManagerName, mgr.Title as MamagerJobTitle
From Employees E
Left join Employees mgr on E.EmployeeID = mgr.ReportsTo
Order By ReportsTo
Note: As per my understanding, ReportsTo column has employeeID who have report to any manger, if not please replace that column with mgr.ReportsTo