I have some data in a base table equivalent to:
EmployeeID,Department,Role,Location
-----------------------------------
001,HR,Support,Bristol
002,Banking,Partner,Sheffield
etc.
I then have an additional three tables that contain any additional department, role or location when it is applicable to an individual, but these are simply populated with the EmployeeID and additional piece of information, e.g.
EmployeeID,Location
-------------------
001,London
Where it is populated, I need to take every permutation, for example if the person had an additional location of London, as above, I'd expect to generate two rows:
001,HR,Support,Bristol
001,HR,Support,London
If the person had an additional role as a secretary, I'd expect four rows, however:
001,HR,Support,Bristol
001,HR,Support,London
001,HR,Secretary,Bristol
001,HR,Secretary,London
For clarity, I'd expect to see any record in the base table that does not match in the multiple table too. So fundamentally in the above example I'd expect five rows:
001,HR,Support,Bristol
001,HR,Support,London
001,HR,Secretary,Bristol
001,HR,Secretary,London
002,Banking,Partner,Sheffield
Hopefully that makes some sense
Thanks in advance.
with departmentX as
(
select EmployeeID, Department from Employee
union
select EmployeeId, Department from Department
),
roleX as
(
select EmployeeID, Role from Employee
union
select EmployeeId, Role from Role
),
locationX as
(
select EmployeeID, Location from Employee
union
select EmployeeId, Location from Location
)
select d.EmployeeId, d.department, r.Role, l.Location
from
departmentX d join roleX r
on d.EmployeeId = r.EMployeeId
join locationX l on d.employeeId = l.EmployeeId
I think I've got an solution - you can do it by using small steps:
1) Find every Departments for the employees
SELECT EmployeeID, Department FROM dbo.additionalDepartments
UNION
SELECT EmployeeID, Department FROM dbo.baseTable
2) Find every Roles for the employees
SELECT EmployeeID, Role FROM dbo.additionalRoles
UNION
SELECT EmployeeID, Role FROM dbo.baseTable
3) Find every Locations for the employees
SELECT EmployeeID, Location FROM dbo.additionalLocations
UNION
SELECT EmployeeID, Location FROM dbo.baseTable
4) Join them all :)
SELECT D.EmployeeID, D.Department, R.Role, L.Location FROM
(
SELECT EmployeeID, Department FROM dbo.additionalDepartments
UNION
SELECT EmployeeID, Department FROM dbo.baseTable
) D
INNER JOIN
(
SELECT EmployeeID, Role FROM dbo.additionalRoles
UNION
SELECT EmployeeID, Role FROM dbo.baseTable
) R ON D.EmployeeID = R.EmployeeID
INNER JOIN
(
SELECT EmployeeID, Location FROM dbo.additionalLocations
UNION
SELECT EmployeeID, Location FROM dbo.baseTable
) L ON D.EmployeeID = L.EmployeeID
Please check my SQL Fiddle. I've built your scenario at http://sqlfiddle.com/#!18/756fd/2/0
Related
I was working through some subquery questions and code below was provided as the answer.
my question:
if the inner query returns two minimum salaries that are the same, but belong to different departments. how will the outer query interpet this? will it recognize that salaries refer to different departments?
SELECT first_name, last_name, salary, department_id
FROM employees
WHERE salary IN ( SELECT MIN(salary)
FROM employees
GROUP BY department_id );
thank you
No, it does not know anything about the department information. You need to change the IN to a JOIN:
SELECT e.first_name, e.last_name, e.salary, e.department_id
FROM employees e
INNER JOIN ( SELECT department_id,IN(salary) AS salary
FROM employees
GROUP BY department_id) s
ON s.department_id=e.department_id
AND s.salary=e.salary;
As the statement is currently written it is going to show you each record from employees table which matches the minimum salary for each department with the salary itself. So, the outer query doesn't know anything about the department_id, meaning that there is no correlation between this attribute from the inner query with the outer query at all. You would need to change your logic for example to a JOIN to achieve that.
You can use rank() :
select e.*
from (select e.*, rank() over (partition by e.department_id order by e.salary) as seq
from employees e
) e
where e.seq = 1;
You can pass additional information/condition like this for the department.
CREATE TABLE employees (
empname VARCHAR(20)
,salary DECIMAL(18, 2)
,department_id INT
)
INSERT INTO employees
VALUES ('A', 100,1), ('B', 100, 2), ('C', 300, 2)
SELECT *
FROM employees
WHERE salary IN (
SELECT MIN(salary)
FROM employees
GROUP BY department_id
HAVING department_id = 2
)
AND department_id = 2;
Here is the output
empname salary department_id
-----------------------------
B 100.00 2
Although, 100.00 is the minimum salary for both A and B, but you have passed information for department id: 2. So, only B has come in the output.
I have 36 Sales tables each referred to one store:
st1.dbo.Sales
st2.dbo.Sales
...
st35.dbo.Sales
st36.dbo.Sales
Each record has the following key columns:
UserName, PostalCode, Location, Country, InvoiceAmount, ItemsCount, StoreID
Here is SQLFiddle
I need to copy into Customers table all Username (and their details) that are not already present into Customers
in case of duplicated it is required to use the fields of record where InvoiceAmount is MAX
I tried to build a query but looks too complicated and it is also wrong because in CROSS APPLY should consider the full list of Sales Tables
INSERT INTO Customers (.....)
SELECT distinct
d.UserName,
w.postalCode,
w.location,
W.country,
max(w.invoiceamount) invoiceamount,
max(w.itemscount) itemscount,
w.storeID
FROM
(SELECT * FROM st1.dbo.Sales
UNION
SELECT * FROM st2.dbo.Sales
UNION
...
SELECT * FROM st36.dbo.Sales) d
LEFT JOIN
G.dbo.Customers s ON d.Username = s.UserName
CROSS APPLY
(SELECT TOP (1) *
FROM s.dbo.[Sales]
WHERE d.Username=w.Username
ORDER BY InvoiceAmount DESC) w
WHERE
s.UserName IS NULL
AND d.username IS NOT NULL
GROUP BY
d.UserName, w.postalCode, w.location,
w.country, w.storeID
Can somebody please give some hints?
As a basic SQL query, I'd create a row_number in the inner subquery and then join to customers and then isolated the max invoice number for each customer not in the customer table.
INSERT INTO Customers (.....)
SELECT w.UserName,
w.postalCode,
w.location,
w.country,
w.invoiceamount,
w.itemscount,
w.storeID
FROM (select d.*,
row_number() over(partition by d.Username order by d.invoiceamount desc) rownumber
from (SELECT *
FROM st1.dbo.Sales
UNION
SELECT *
FROM st2.dbo.Sales
UNION
...
SELECT *
FROM st36.dbo.Sales
) d
LEFT JOIN G.dbo.Customers s
ON d.Username = s.UserName
WHERE s.UserName IS NULL
AND d.username IS NOT NULL
) w
where w.rownumber = 1
Using your fiddle this will select distinct usernames rows with max invoiceamount
with d as(
SELECT * FROM Sales
UNION
SELECT * FROM Sales2
)
select *
from ( select *,
rn = row_number() over(partition by Username order by invoiceamount desc)
from d) dd
where rn=1;
step 1 - use cte .
select username , invoiceamount ,itemscount from Sales
UNION all
select user name , invoiceamount ,itemscount from Sales
.....
...
step 2
next cte use group by and get max invoiceamount ,itemscount for user of last result set.
,cte2 as (
select user name , max (invoiceamount) as invoiceamount ,max(itemscount) as itemscount from cte)
step3
use left join with user table and find missing record and itemscount invoiceamount
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 a database Library, which has a lot of tables and we need 3 tables for query:
Table Librarians: ID, Surname;
Table StudentCard: ID, foreign key on table Librarians and other columns which we don't use
Table TeacherCard: ID, foreign key on table Librarians and other columns which we don't use
Query: select the librarian's surname, which gave the most count of books.
I know, how to resolve, when I took data only from one table, e. g. TeacherCard
SELECT TOP 1 WITH TIES
Librarians.LastName, MAX(Librarians.CountOfBooks) AS Books
FROM
(SELECT
L.LastName, COUNT(*) AS CountOfBooks
FROM Libs L, T_Cards T
WHERE T.Id_Lib IN (SELECT L.Id)
GROUP BY L.LastName) AS Librarians
GROUP BY
Librarians.LastName
ORDER BY
MAX(Librarians.CountOfBooks) DESC
GO
I don't know, how to use data from TeacherCard and from StudetnCard at the same time.
Please, help to write this query.
I have a right resolving !!!!
SELECT TOP 1 B.Name, B.CountOut
FROM
(SELECT A.Name, SUM(A.Count) AS CountOut
FROM
(SELECT Libs.LastName AS Name, COUNT(S_Cards.DateOut) AS [Count]
FROM Libs JOIN S_Cards ON S_Cards.Id_Lib = Libs.Id
GROUP BY Libs.LastName
UNION ALL
SELECT Libs.LastName AS Name, COUNT(T_Cards.DateOut) AS [Count]
FROM Libs JOIN T_Cards ON T_Cards.Id_Lib = Libs.Id
GROUP BY Libs.LastName) AS A
GROUP BY A.Name ) AS B
ORDER BY B.CountOut DESC
I have another right answer:
SELECT TOP 2 LastName, COUNT (*) [count] FROM
(SELECT LastName FROM Libs L, S_Cards S
WHERE S.id_lib = L.id
UNION ALL
SELECT LastName FROM Libs L, T_Cards T
WHERE T.id_lib = L.id) As Res
GROUP By LastName
ORDER BY COUNT (*) DESC
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.