I have the following tables
Users
Id
FirstName
LastName
Sample Data
1,'Peter','Smith'
2,'John','Como'
Phones
Id
UserId
PhoneTypeId
Phone
ContactName
Sample data
1,1,4,'555-555-5551','Peter'
2,1,4,'555-555-5552','Paul'
3,1,4,'555-555-5553','Nancy'
4,1,4,'555-555-5554','Hellen'
PhoneTypes
Id
Type
with sample data
1 Home
2 Work
3 Cell
4 Emergency
I have to create following result
UserId, UserFirstName, UserLastName, FirstEmergencyContactName, FirstEmergencyContactPhone, SecondEmergencyContactName, SecondEmergencyContactPhone, ThirdEmergencyContactName, ThirdEmergencyContactPhone, FourthEmergencyContactName, FourthEmergencyContactPhone, FifthEmergencyContactName, FifthEmergencyContactPhone
How can I create a single row for every user with emergency contacts? Some of the users might have one emergency contact and others might have many, but I need only five of them.
This is called table pivoting. Since you want no more than 5 results, you can use conditional aggregation with row_number:
select id, firstname, lastname,
max(case when rn = 1 then contactname end) emergency_contact1,
max(case when rn = 1 then phone end) emergency_phone1,
max(case when rn = 2 then contactname end) emergency_contact2,
max(case when rn = 2 then phone end) emergency_phone2,
...
from (
select u.id, u.firstname, u.lastname, p.phone, p.contactname,
row_number() over (partition by u.id order by p.phonetypeid) rn
from users u
join phones p on u.id = p.userid
) t
group by id, firstname, lastname
Also you can use pivoting, without dynamic SQL and hard-coding, because you need only 5 contacts/phones. Example below:
;WITH cte AS (
SELECT p.UserId,
FirstName,
LastName,
CAST(ContactName as nvarchar(100)) as ContactName,
CAST(Phone as nvarchar(100)) as ContactPhone,
CAST(ROW_NUMBER() OVER (PARTITION BY p.UserId ORDER BY pt.Id) as nvarchar(100)) as RN
FROM Users u
INNER JOIN Phones p
ON p.UserId = u.Id
INNER JOIN PhoneTypes pt
ON pt.Id = p.PhoneTypeId
WHERE pt.Id = 4
)
SELECT *
FROM (
SELECT UserId,
FirstName,
LastName,
[Columns]+RN as [Columns],
[Values]
FROM cte
UNPIVOT (
[Values] FOR [Columns] IN (ContactName, ContactPhone)
) as unp
) as t
PIVOT (
MAX([Values]) FOR [Columns] IN (ContactName1,ContactPhone1,ContactName2,ContactPhone2,ContactName3,ContactPhone3,
ContactName4,ContactPhone4,ContactName5,ContactPhone5)
) as pvt
Output:
UserId FirstName LastName ContactName1 ContactPhone1 ContactName2 ContactPhone2 ContactName3 ContactPhone3 ContactName4 ContactPhone4 ContactName5 ContactPhone5
1 Peter Smith Peter 555-555-5551 Paul 555-555-5552 Nancy 555-555-5553 Hellen 555-555-5554 NULL NULL
2 John Cono Harry 555-555-5555 William 555-555-5556 John 555-555-5557 NULL NULL NULL NULL
I add some more contacts.
Related
I have a table like the below
Opp_ID Role_Name Role_User_Name
---------------------------------------
1 Lead Person_one
1 Developer Person_two
1 Developer Person_three
1 Owner Person_four
1 Developer Person_five
I now need to split the Role_Name column to be 3 different columns based on the values. I need to make sure there are no NULL values so the table should like the below
Opp_ID Lead Developer Owner
--------------------------------------------------
1 Person_one Person_two Person_four
1 Person_one Person_three Person_four
1 Person_one Person_five Person_four
My code is currently:
SELECT
ID,
CASE WHEN Role_Name = 'Lead' THEN Role_User_Name ELSE NULL END AS Lead,
CASE WHEN Role_Name = 'Developer' THEN Role_User_Name ELSE NULL END AS Developer,
CASE WHEN Role_Name = 'Owner' THEN Role_User_Name ELSE NULL END AS Owner
FROM
[table1]
WHERE
Role_Name IN ('Lead','Developer','Owner')
Unfortunately this returns these results:
Opp_ID Lead Developer Owner
-------------------------------------------
1 Person_one NULL NULL
1 NULL Person_two NULL
1 NULL Person_three NULL
1 NULL NULL Person_four
1 NULL Person_five NULL
I assume to get this working you need to join the code back on itself but I can't seem to get it working.
To apply each developer and lead across your owners for an Opp_ID, you'll want something like:
SELECT o.opp_id
, o.Role_User_Name AS Owner
, l.Role_User_Name AS Lead
, d.Role_User_Name AS Developer
FROM t1 AS o
LEFT OUTER JOIN t1 l ON o.opp_id = l.opp_id AND l.Role_Name = 'Lead'
LEFT OUTER JOIN t1 d ON o.opp_id = d.opp_id AND d.Role_Name = 'Developer'
WHERE o.Role_Name = 'Owner'
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=da4daea062534245bed474f93ffafbb7
You can just switch to aggregation:
SELECT ID,
MAX(CASE WHEN Role_Name = 'Lead' THEN Role_User_Name END) AS Lead,
MAX(CASE WHEN Role_Name = 'Developer' THEN Role_User_Name END) AS Developer,
MAX(CASE WHEN Role_Name = 'Owner' THEN Role_User_Name END) AS Owner
FROM [table1]
WHERE Role_Name IN ('Lead', 'Developer', 'Owner')
GROUP BY ID;
If you could have multiple people, you might want to use STRING_AGG().
Note that I removed the ELSE NULL. This is redundant. With no ELSE clause, the CASE expression returns NULL when there is no match.
You can also use first_value function on Pivot results like below
See working demo
select
opp_id,
lead=COALESCE([lead],FIRST_VALUE([Lead]) over( order by opp_id )),
Developer=COALESCE([Developer],FIRST_VALUE([Developer]) over( order by opp_id )),
Owner=COALESCE([Owner],FIRST_VALUE([Owner]) over( order by opp_id ))
from
(select opp_id,Role_Name,
Role_User_Name,
rn=row_number() over( partition by Role_Name order by (select 1))
from
table1)
src
pivot
(max(Role_user_name) for role_name in ([Lead],[Developer],[Owner]))p
I prefer to use cross join
select o.opp_id,
o.role_user_name o,
l.role_user_name l,
d.role_user_name d
from t1 o cross join t1 l cross join t1 d
where o.role_name = 'Owner'
and l.role_name = 'Lead'
and d.role_name = 'Developer'
enter image description here
I have 2 tables.
Contacts
ContactID pk
EmailAddress
FirstName
LastName
Address
Orders
OrderID pk
ContactID fk
I want to get the number or orders for each email address in Contacts like below
select
Contacts.EmailAddress,
count(distinct Orders.OrderID) AS NumOrders
from
Contacts inner join Orders on Contacts.ContactID = Orders.ContactID
group by
Contacts.EmailAddress
Problem is, I also want the first name, last name, address. But I can't group by those because each email address in Contacts could have a different first name, lastname or address associated with it.
ie:
myname#email.com, Fred, Jackson, 123 Main St
myname#email.com, Bob, Smith, 456 Spruce St.
How can I change my query so that I can get the first name, last name and address for the most recent entry made in Contacts for that email address?
Thanks in advance!
My first thought would be to use windowed functions.
SELECT EmailAddress,
FirstName,
Lastname,
[Address],
EmailOrderCount
FROM (SELECT c.EmailAddress,
c.FirstName,
c.LastName,
c.[Address],
COUNT(o.OrderID) OVER (PARTITION BY c.EmailAddress) EmailOrderCount,
ROW_NUMBER() OVER (PARTITION BY c.EmailAddress ORDER BY c.ContactID DESC) Rn
FROM Contacts c
JOIN Orders o ON c.ContactID = o.ContactID
) t
WHERE Rn = 1
Demo
another way would be to use CROSS APPLY to append the top 1 contact record to the summary rows.
SELECT c.EmailAddress,
COUNT(o.OrderID) NumOrders,
ca.FirstName,
ca.LastName,
ca.[Address]
FROM Contacts c
INNER JOIN Orders ON c.ContactId = o.ContactID
CROSS APPLY (
SELECT TOP 1
FirstName,
Lastname,
[Address]
FROM Contacts c2
WHERE c2.EmailAddress = c.EmailAddress
ORDER BY c2.ContactID DESC) ca
GROUP BY c.EmailAddress,
ca.FirstName,
ca.LastName,
ca.[Address]
Try this:
select
Contacts.Name,
Contacts.FirstName,
Contacts.LastName
Contacts.EmailAddress,
count(distinct Orders.OrderID) AS NumOrders
from
(
select max(ContactID) as ContactID,
EmailAddress
from Contacts
group by EmailAddress
) MinContactForEachEMailAddress
inner join
Contacts
on MinContactForEachEMailAddress.ContactID = Contacts.ContactID
inner join
Orders
on Contacts.ContactID = Orders.ContactID
group by
Contacts.EmailAddress
Another way to get what you want is using a CTE and taking the "maximum" row by using ROW_NUMBER.
;WITH CTE AS (
SELECT C.ContactId, C.Name, C.FirstName, C.LastName, C.EmailAddress,
ROW_NUMBER() OVER (PARTITION BY EmailAddress ORDER BY ContactId DESC) RowNo
FROM Contact C
)
SELECT CTE.*, COUNT(o.OrderID) OVER (PARTITION BY CTE.EmailAddress) Cnt
FROM CTE
JOIN Orders O on CTE.ContactID = O.ContactID
-- select the "maximum" row
WHERE CTE.RowNo = 1
An easy way to do this is to make your original query a subquery and select from it. I'm making a slight change, because it's a better practice to group by your primary key than your email address. (Is it a safe bet that each contact has just one email address, and that the basic intent is to group by person?) If so, try this:
SELECT DISTINCT c.EmailAddress, c.FirstName, c.LastName, c.Address, sub.NumOrders
FROM
(
select
Contacts.ContactID
count(distinct Orders.OrderID) AS NumOrders
from
Contacts inner join Orders on Contacts.ContactID = Orders.ContactID
group by
Contacts.ContactID
) sub
JOIN Contacts c
ON sub.ContactID = c.ContactID
If you really need to group by email address instead, then change the above subquery to your original query and change c.EmailAddress to sub.EmailAddress. Of course you may order the SELECT fields however best suits you.
Edit follows:
The ContactID must be a sequence number and you can continually put the same person in the table. So if you add the DISTINCT keyword in the outer query I believe that will give you what you need.
I have an Employee table like this
And a second table for EmployeeComments with the EmployeeID as foreign key:
I would like to query the employees with their comments in the following format:
select Name, Comment
from Employee emp
left join EmployeeComments empC on empC.EmployeeID = emp.ID
I would like the results to be like:
I have already looked at Pivot, but it doesn't resolve my issue
Use window function:
select case when row_number() over(partition by emp.name order by empC.ID) = 1
then Name
else '' end as Name,
Comment
from Employee emp
left join EmployeeComments empC On empC.EmployeeID = emp.ID
This might give you some kind of order in your result window at least
WITH cte AS(
SELECT emp.Name ,
empC.Comment,
RANK() OVER (ORDER BY emp.Name) NameOrder,
ROW_NUMBER() OVER (PARTITION BY emp.Name ORDER BY empC.ID) RN
FROM Employee emp
LEFT JOIN EmployeeComments empC ON empC.EmployeeID = emp.ID
)
SELECT
Name = (CASE WHEN RN = 1 THEN Name ELSE '' END),
Comment
FROM
cte
ORDER BY
NameOrder,
RN
"use Cross Join:"
Query:
select case t.cnt
when 1 then
coalesce(t.Name,' ')
end as Name,t.comment
from
(
select t1.Name,t2.comment,row_number()
over(partition by t1.name order by t1.Name)
as cnt
from
EmployeeComments t1
cross join
Employee t2
where t1.ID=t2.Employeeid
)t
I have a situation where I need to be able to see if a given person is within a user/manager hierarchy.
I have the next structure of table:
UserId
UserName
ManagerId
I have 2 IDs: some UserId (say 5) and ManagerId (say 2). As a result I need to know if manager with given Id (2) is chief for user with given id (5)? For example, if
User 1 reports to user 2.
User 3 reports to user 1.
User 4 reports to user 3
the result SQL-query have to show that for UserId = 4 and ManagerId = 1 answer is true.
I've just created query for getting all hierarchy:
WITH temp (level, UserName, UserId, ManagerId) AS
(
SELECT 1 AS level, EmployeeName, EmployeeId, BossId
FROM Employees
WHERE BossId IS NULL
UNION ALL
SELECT level+1 AS level, EmployeeName, EmployeeId, BossId
FROM Employees, temp
WHERE BossId = UserId
)
SELECT t.* from temp AS t
But now I don't know how to get result query with above mentioned conditions :(
Thanks in advance for any help!
Find the user in the anchor and walk your way back up the hierarchy. Check the rows you have got in the recursive query against the manager.
This will return the manager row if there exist one.
WITH temp AS
(
SELECT EmployeeName, EmployeeId, BossId
FROM Employees
WHERE EmployeeId = #UserID
UNION ALL
SELECT E.EmployeeName, E.EmployeeId, E.BossId
FROM Employees AS E
inner join temp AS T
ON E.EmployeeId = T.BossId
)
SELECT *
FROM temp
WHERE EmployeeId = #ManagerID
This will return the BossID if he or she exist:
WITH BOSSES AS
(
SELECT BossID
FROM Employees
WHERE EmployeeID = #uID
UNION ALL
SELECT E.BossID
FROM Employees E
JOIN BOSSES B ON E.EmployeeID = B.BossID
)
SELECT *
FROM BOSSES
WHEN BossID = #bID
I've included the hierarchy of all levels with the CTE that you can then use to query. Using this hierarchy, you can see all the managers of a given employee in a delimited column (might be useful for other calculations).
Give this a try:
WITH cte (UserId, ManagerId, Level, Hierarchy) as (
SELECT EmployeeId, BossId, 0, CAST(EmployeeId as nvarchar)
FROM Employee
WHERE BossId IS NULL
UNION ALL
SELECT EmployeeId, BossId, Level+1,
CAST(cte.Hierarchy + '-' + CAST(EmployeeId as nvarchar) as nvarchar)
FROM Employee INNER JOIN cte ON Employee.BossId=cte.UserId
)
SELECT *
FROM cte
WHERE UserId = 4
AND '-' + Hierarchy LIKE '%-1-%'
And here is the Fiddle. I've used UserId = 4 and ManagerId = 1.
Good luck.
I have a SQL Server 2008 database. This database has two tables called Customer and Order. These tables are defined as follows:
Customer
--------
ID,
First Name,
Last Name
Order
-----
ID,
CustomerID,
Date,
Description
I am trying to write a query that returns all of the customers in my database. If the user has placed at least one order, I want to return the information associated with the most recent order placed. Currently, I have the following:
SELECT
*
FROM
Customer c LEFT OUTER JOIN Order o ON c.[ID]=o.[CustomerID]
As you can imagine, this will return all of the orders associated with a customer. In reality though, I only want the most recent one. How do I do this in SQL?
Thank you!
Here's a method that doesn't assume that the order dates are unique:
SELECT
Customer.ID CustomerID,
Customer.FirstName,
Customer.LastName,
T1.ID OrderID,
T1.Date OrderDate,
T1.Description OrderDescription
FROM Customer
LEFT JOIN (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY Date DESC) AS rn
FROM [Order]
) T1
ON Customer.ID = T1.CustomerID AND T1.rn = 1
Result:
CustomerID FirstName LastName OrderID OrderDate OrderDescription
1 FirstName1 LastName1 2 2010-05-02 Description2
2 FirstName2 LastName2 3 2010-05-03 Description3
3 FirstName3 LastName3 NULL NULL NULL
Test data:
CREATE TABLE Customer (ID INT NOT NULL, FirstName VARCHAR(100) NOT NULL, LastName VARCHAR(100) NOT NULL);
INSERT INTO Customer (ID, FirstName, LastName) VALUES
(1, 'FirstName1', 'LastName1'),
(2, 'FirstName2', 'LastName2'),
(3, 'FirstName3', 'LastName3');
CREATE TABLE [Order] (ID INT NOT NULL, CustomerID INT NOT NULL, Date DATE NOT NULL, Description NVARCHAR(100) NOT NULL);
INSERT INTO [Order] (ID, CustomerID, Date, Description) VALUES
(1, 1, '2010-05-01', 'Description1'),
(2, 1, '2010-05-02', 'Description2'),
(3, 2, '2010-05-03', 'Description3'),
(4, 2, '2010-05-03', 'Description4');
select c.ID, c.FirstName, c.LastName, o.ID as OrderID, o.Date, o.Description
from Customer c
left outer join (
select CustomerID, max(Date) as MaxDate
from Order
group by CustomerID
) om on c.ID = om.CustomerID
left outer join Order o on om.CustomerID = o.CustomerID and om.MaxDate = o.Date
I would use where clause with Max() function to guarantee the latest added record:
(you code...)
Where o.id = max(o.id)
Select * from
Customer C Left join
(
Select o.CustomerID, Description, Date from
Orders o inner join
(
Select CustomerID, Max(Date) as LastOrder
From Orders Group by CustomerID
) SubLatest on o.CustomerID = SubLatest.CustomerID
and o.Date = SubLatest.LastOrder
) SubDetails
on C.id = SubDetails.CustomerID
Not Homework I hope! :O)
Select Top 1 C.*
,O.*
From Customer C left outer join
Order O on O.CustomerId = C.Id
Order by O.[Date] Desc
Hope that helps