Recursive CTE with additional EXISTS conditions? - sql-server

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.

Related

Permutations of multiple data items in T-SQL

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

SQL Server 2014 Consolidate Tables avoiding duplicates

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

Select Maximum, using filelds from 2 tables

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

how to retrieve data from 3 tables by checking if one public column value is present in a result list

I have 5 tables:
Users, countries ,Houses, Cars and Computers.
User table has the following columns:
- Id
- Name
- CountryId
Houses table has the following columns:
- Id
- UserId
- CountryLocationId
Cars table have has following columns:
- Id
- UserId
- CountryLocationId
Computers table has the following columns:
- Id
- UserId
- CountryLocationId
Countries table has the following columns:
- Id
- Name
I want to do something like this:
select distinct(Id) from Users where Id in(
select UserId from Houses where ContryLocationId in (select Id from Countries)
select UserId from Cars where ContryLocationId in (select Id from Countries)
select UserId from Computers where ContryLocationId in (select Id from Countries)
)
but i want to re-use the result from (select Id from Countries) instead of call it again.
I'd probably use a temp table.
SELECT INTO TEMP_COUNTRIES (select Id from Countries)
Then you can join to that for your remaining queries:
select UserId from Cars C
inner join temp_countries tc
on c.ContryLocationId = tc.id
I know using IN operator again and again will hurt the performance but you can use the better option of using EXISTS operator.
;WITH CTE
AS
(
select UserId, ContryLocationId from Houses
UNION ALL
select UserId, ContryLocationId from Cars
UNION ALL
select UserId, ContryLocationId from Computers
)
select distinct(U.Id)
from Users U
WHERE EXISTS (
SELECT 1
FROM CTE C
WHERE C.UserId = U.ID
AND EXISTS (
SELECT 1
FROM Countries
WHERE C.ContryLocationId = Countries.Id
)
)
OR
;WITH CTE
AS
(
select UserId from Houses WHERE EXISTS (SELECT 1
FROM Countries
WHERE Houses.ContryLocationId = ID)
UNION ALL
select UserId from Cars WHERE EXISTS (SELECT 1
FROM Countries
WHERE Houses.ContryLocationId = ID)
UNION ALL
select UserId from Computers WHERE EXISTS (SELECT 1
FROM Countries
WHERE Houses.ContryLocationId = ID)
)
select distinct(U.Id)
from Users U
WHERE EXISTS (SELECT 1
FROM CTE C
WHERE C.UserId = U.ID
)

Finding Duplicate Data in Oracle

I have a table with 500,000+ records, and fields for ID, first name, last name, and email address. What I'm trying to do is find rows where the first name AND last name are both duplicates (as in the same person has two separate IDs, email addresses, or whatever, they're in the table more than once). I think I know how to find the duplicates using GROUP BY, this is what I have:
SELECT first_name, last_name, COUNT(*)
FROM person_table
GROUP BY first_name, last_name
HAVING COUNT(*) > 1
The problem is that I need to then move the entire row with these duplicated names into a different table. Is there a way to find the duplicates and get the whole row? Or at least to get the IDs as well? I tried using a self-join, but got back more rows than were in the table to begin with. Would that be a better approach? Any help would be greatly appreciated.
The most effective way to remove duplicate rows is with a self-join:
DELETE FROM person_table a
WHERE a.rowid >
ANY (SELECT b.rowid
FROM person_table b
WHERE a.first_name = b.first_name
AND a.last_name = b.last_name);
This will remove all duplicates even if there are more than one duplicate row.
There is more on removing duplicates and differing methods here: http://www.dba-oracle.com/t_delete_duplicate_table_rows.htm
Hope it helps...
EDIT: As per your comments, if you want to select all but one of the duplicates then
SELECT *
FROM person_table a
WHERE a.rowid >
ANY (SELECT b.rowid
FROM person_table b
WHERE a.first_name = b.first_name
AND a.last_name = b.last_name);
An index on (first_name, last_name) or on (last_name, first_name) would help:
SELECT t.*
FROM
person_table t
JOIN
( SELECT first_name, last_name
FROM person_table
GROUP BY first_name, last_name
HAVING COUNT(*) > 1
) dup
ON dup.last_name = t.last_name
AND dup.first_name = t.first_name
or:
SELECT t.*
FROM person_table t
WHERE EXISTS
( SELECT *
FROM person_table dup
WHERE dup.last_name = t.last_name
AND dup.first_name = t.first_name
AND dup.ID <> t.ID
)
This will give you an ID you want to move/delete/etc. Note that it does not work if count(*) > 2, as you get only 1 ID (you could re-run your query for these cases).
SELECT max(ID), first_name, last_name, COUNT(*)
FROM person_table
GROUP BY first_name, last_name
HAVING COUNT(*) > 1
Edit: You can use COLLECT to get all IDs at once (but be careful, as you only want to move/delete all but one)
To add another option, I usually use this one to remove duplicates:
delete from person_table
where rowid in (select rid
from (select rowid rid, row_number() over
(partition by first_name,last_name order by rowid) rn
from person_table
)
where rn <> 1 )

Resources