SQL Recursive Coalesce - sql

I'm trying to create a column that contains all cities of the referenced addresses.
DECLARE #AddressList nvarchar(max)
SELECT #AddressList = COALESCE(#AddressList + ' ', '') + City FROM [Address]
SELECT
Employee.*,
(SELECT #AddressList) AS AddressCities
FROM Employee
But I dont know where to put the WHERE clause.
...
(SELECT #AddressList WHERE EmployeeId = Employee.EmployeeId) AS AddressCities
...
The above test doesnt work..
Table schemas are:
Employee
EmployeeId
Name
Address
Street
City
EmployeeId

If i understand you correctly, you wish to show all Cities in a single column for the employee. So you wish to GROUP BY and CONCAT.
Using Sql Server 2005, try this (working example)
DECLARE #Employee TABLE(
EmployeeId INT,
NAME VARCHAR(100)
)
INSERT INTO #Employee (EmployeeId,[NAME]) SELECT 1, 'A'
INSERT INTO #Employee (EmployeeId,[NAME]) SELECT 2, 'B'
DECLARE #Address TABLE(
Street VARCHAR(50),
City VARCHAR(50),
EmployeeId INT
)
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'A','A', 1
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'B','B', 1
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'C','C', 1
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'D','D', 2
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'E','E', 2
INSERT INTO #Address (Street,City, EmployeeId) SELECT 'F','F', 2
SELECT e.EmployeeId,
e.[NAME],
(
SELECT al.City + ','
FROM #Address al
WHERE al.EmployeeId = e.EmployeeId
FOR XML PATH('')
)
FROM #Employee e
GROUP BY e.EmployeeId,
e.[NAME]

Do need more information about what you mean by 'column that contains all cities'. How is what you want different to the following might help you phrase the question
SELECT e.EmployeeId,e.Name,a.City
FROM Employee e
INNER JOIN Address a ON a.EmployeeId = e.EmployeeId
GROUP BY e.EmployeeId,e.Name
-- update
I think I see what you mean, do you want like:
EmployeeID | Name | Address
1 | John | 'London','Paris','Rome'
2 | Jane | 'New York','Miami'
?

Related

Query different type data in to a single row with the master record

I just created the following sample data to demonstrate what I am after.
I need to query the above table to get my information as 1 single row
i.e. like the following
I was planning to create two temp tables and insert the two different types of addresses seperately. Then, inner join them with the main company table. I am not sure, this is a good solution. I appreaciate if anyone share their thoughts or code to my problem.
Try this..
Select companyId,CompanyName,homesddress1
,homeaddress2,HomePostCode,OfficeAddress1,OfficeAddress2,OfficePostCode
From tblCompany a
Outer apply ( select address1 homesddress1, address2 homeaddress2,postcode HomePostCode
From tblAddress t
Where AddressType='home' and t.companyid=a.companyid)
Outer apply (select address1 OfficeAddress1, address2 Officeaddress2,postcode OfficePostCode
From tblAddress t2
Where AddressType='Office ' and t2.companyid=a.companyid)
You can do it with a simple select using two outer joins. Note that you need the joins to be outer because for some companies you may only have one address.
DECLARE #company TABLE (
CompanyId int,
CompanyName varchar(50)
)
DECLARE #companyAddress TABLE (
Id int,
AddressType varchar(10),
Address1 varchar(50),
Address2 varchar(50),
Postcode varchar(10),
CompanyId int
)
INSERT INTO #company VALUES (1, 'Test Company')
INSERT INTO #companyAddress VALUES (1, 'Home', '25 Street', 'City 1', 'BA3 1PE', 1)
INSERT INTO #companyAddress VALUES (2, 'Office', '25 Street', 'City 2', 'NA1 4TW', 1)
SELECT c.CompanyId, c.CompanyName,
h.Address1 AS HomeAddress1, h.Address2 AS HomeAddress2, h.Postcode AS HomePostcode,
o.Address1 AS OfficeAddress1, o.Address2 AS OfficeAddress2, o.Postcode AS OfficePostcode
FROM #company c
LEFT JOIN
#companyAddress h ON h.CompanyId = c.CompanyId AND h.AddressType = 'Home'
LEFT JOIN
#companyAddress o ON o.CompanyId = c.CompanyId AND o.AddressType = 'Office'
Here is an Almost Dynamic version. You just have to specify the field list in the final pivot
Declare #YourTable table (ID int,AddressType varchar(25),Address1 varchar(50),Address2 varchar(50),CompanyID int)
Insert Into #YourTable values
(1,'Home' ,'25 Street','City 1',1),
(2,'Office','10 Avenue','City 2',1)
Declare #XML xml = (Select * from #YourTable for XML RAW) --<<< Initial Query
;with cteBase as (
Select ID = R.value('#CompanyID','int') --<<< Key ID
,AddressType = R.value('#AddressType','varchar(50)')
,Item = R.value('#AddressType','varchar(50)')+Attr.value('local-name(.)','varchar(100)')
,Value = Attr.value('.','varchar(max)')
From #XML.nodes('/row') as A(R)
Cross Apply A.r.nodes('./#*[local-name(.)!="CompanyID"]') as B(Attr) --<<< Key ID
),cteDist as (Select Distinct ID,Item from cteBase
),cteComp as (
Select A.*,B.Value
From cteDist A
Cross Apply (Select Value=Stuff((Select Distinct ',' + Value
From cteBase
Where ID=A.ID
and Item=A.Item
For XML Path ('')),1,1,'') ) B
)
Select *
From (Select * From cteComp) as s
Pivot (max(value)
For Item in (HomeID,HomeAddressType,HomeAddress1,HomeAddress2,OfficeID,OfficeAddressType,OfficeAddress1,OfficeAddress2)) as pvt
Returns
ID HomeID HomeAddressType HomeAddress1 HomeAddress2 OfficeID OfficeAddressType OfficeAddress1 OfficeAddress2
1 1 Home 25 Street City 1 2 Office 10 Avenue City 2

Remove duplicates with less null values

I have a table of employees which contains about 25 columns. Right now there are a lot of duplicates and I would like to try and get rid of some of these duplicates.
First, I want to find the duplicates by looking for multiple records that have the same values in first name, last name, employee number, company number and status.
SELECT
firstname,lastname,employeenumber, companynumber, statusflag
FROM
employeemaster
GROUP BY
firstname,lastname,employeenumber,companynumber, statusflag
HAVING
(COUNT(*) > 1)
This gives me duplicates but my goal is to find and keep the best single record and delete the other records. The "best single record" is defined by the record with the least amount of NULL values in all of the other columns. How can I do this?
I am using Microsoft SQL Server 2012 MGMT Studio.
EXAMPLE:
Red: DELETE
Green: KEEP
NOTE: There are a lot more columns in the table than what this table shows.
You can use the sys.columns table to get a list of columns and build a dynamic query. This query will return a 'KeepThese' value for every record you want to keep based on your given criteria.
-- insert test data
create table EmployeeMaster
(
Record int identity(1,1),
FirstName varchar(50),
LastName varchar(50),
EmployeeNumber int,
CompanyNumber int,
StatusFlag int,
UserName varchar(50),
Branch varchar(50)
);
insert into EmployeeMaster
(
FirstName,
LastName,
EmployeeNumber,
CompanyNumber,
StatusFlag,
UserName,
Branch
)
values
('Jake','Jones',1234,1,1,'JJONES','PHX'),
('Jake','Jones',1234,1,1,NULL,'PHX'),
('Jake','Jones',1234,1,1,NULL,NULL),
('Jane','Jones',5678,1,1,'JJONES2',NULL);
-- get records with most non-null values with dynamic sys.column query
declare #sql varchar(max)
select #sql = '
select e.*,
row_number() over(partition by
e.FirstName,
e.LastName,
e.EmployeeNumber,
e.CompanyNumber,
e.StatusFlag
order by n.NonNullCnt desc) as KeepThese
from EmployeeMaster e
cross apply (select count(n.value) as NonNullCnt from (select ' +
replace((
select 'cast(' + c.name + ' as varchar(50)) as value union all select '
from sys.columns c
where c.object_id = t.object_id
for xml path('')
) + '#',' union all select #','') + ')n)n'
from sys.tables t
where t.name = 'EmployeeMaster'
exec(#sql)
Try this.
;WITH cte
AS (SELECT Row_number()
OVER(
partition BY firstname, lastname, employeenumber, companynumber, statusflag
ORDER BY (SELECT NULL)) rn,
firstname,
lastname,
employeenumber,
companynumber,
statusflag,
username,
branch
FROM employeemaster),
cte1
AS (SELECT a.firstname,
a.lastname,
a.employeenumber,
a.companynumber,
a.statusflag,
Row_number()
OVER(
partition BY a.firstname, a.lastname, a.employeenumber, a.companynumber, a.statusflag
ORDER BY (CASE WHEN a.username IS NULL THEN 1 ELSE 0 END +CASE WHEN a.branch IS NULL THEN 1 ELSE 0 END) )rn
-- add the remaining columns in case statement
FROM cte a
JOIN employeemaster b
ON a.firstname = b.firstname
AND a.lastname = b.lastname
AND a.employeenumber = b.employeenumber
AND a.companynumbe = b.companynumber
AND a.statusflag = b.statusflag)
SELECT *
FROM cte1
WHERE rn = 1
I test with MySQL and use NULL String concat to found the best record. Because LENGTH ( NULL || 'data') is 0. Only if all column not NULL some length exists. Maybe this is not perfekt.
create table EmployeeMaster
(
Record int auto_increment,
FirstName varchar(50),
LastName varchar(50),
EmployeeNumber int,
CompanyNumber int,
StatusFlag int,
UserName varchar(50),
Branch varchar(50),
PRIMARY KEY(record)
);
INSERT INTO EmployeeMaster
(
FirstName, LastName, EmployeeNumber, CompanyNumber, StatusFlag, UserName, Branch
) VALUES ('Jake', 'Jones', 1234, 1, 1, 'JJONES', 'PHX'), ('Jake', 'Jones', 1234, 1, 1, NULL, 'PHX'), ('Jake', 'Jones', 1234, 1, 1, NULL, NULL), ('Jane', 'Jones', 5678, 1, 1, 'JJONES2', NULL);
My query idea looks like this
SELECT e.*
FROM employeemaster e
JOIN ( SELECT firstname,
lastname,
employeenumber,
companynumber,
statusflag,
MAX( LENGTH ( username || branch ) ) data_quality
FROM employeemaster
GROUP BY firstname, lastname, employeenumber, companynumber, statusflag
HAVING count(*) > 1
) g
ON LENGTH ( username || branch ) = g.data_quality

MS SQL Stored procedure to get the value

I have 3 tables
Staff table:
EmpId CandidateId
------------------------
1 2
Candidate table:
CandidateId Firstname Last name CountryId PassportCountry
--------------------------------------------------------------------
1 Mark Antony 2 3
2 Joy terry 1 3
Country:
CountryId Name
---------------------------
1 USA
2 UK
3 Australia
User will pass the EmpId in the querystring I need to show the candidate details according to the empId. I have only one country table and using that table for country, passportport country. So I need to get the country name when I get the candidate value.
How to write the stored procedure to get the candidate details. Im not good in sql. Can you guys help me on this. Thanks in advance.
Hi I tried the below script to get the country name and passport country name. I can get the country name, but not the passport country.
SELECT
FirstName,
LastName,
PassportCountry,
Country.CountryName as Country
from Candidate
inner join Country
on country.CountryId=candidate.country
where CandidateId=#CandidateId;
This should get you going in the right direction.
Basically, since you're referencing the Country table twice, you need to join it twice.
declare #staff table (EmpId int, CandidateId int)
insert into #staff values (1, 2 )
declare #Candidate table (CandidateId int, Firstname nvarchar(50), Lastname nvarchar(50), CountryId int, PassportCountry int)
insert into #Candidate
values (1, 'Mark', 'Antony', 2, 3),
(2, 'Joy', 'Terry', 1, 3)
declare #Country table (CountryId int, Name nvarchar(50))
insert into #Country
values (1, 'USA'),
(2, 'UK'),
(3, 'Australia')
declare #empID int
set #empID = 1
SELECT
FirstName,
LastName,
t2.Name as PersonCountry,
t3.Name as PassportCountry
from #staff s
inner join #Candidate t1 on s.CandidateId = t1.CandidateId
inner join #Country t2 on t1.CountryId=t2.CountryId
inner join #Country t3 on t1.PassportCountry=t3.CountryId
where s.EmpId=#empID;

Path enumeration model, how to get superiors?

Consider the table data below:
emp_name VARCHAR(25), path VARCHAR(150)
Albert /Albert
John /Albert/John
Chuck /Albert/Chuck
Tom /Albert/John/Tom
Frank /Frank
I want to get a list of superiors of Tom:
John
Albert
(can include Tom)
Is it possible to do with without splitting the path and then using a sequence table (only way I found)?
DB is Sql server 2008 R2
You can use a recursive CTE to split the hierarchy string value.
declare #T table
(
ID int identity,
emp_name varchar(25),
[path] varchar(150)
)
insert into #T values
('Albert', 'Albert'),
('John', 'Albert/John'),
('Chuck', 'Albert/Chuck'),
('Tom', 'Albert/John/Tom'),
('Frank', 'Frank')
declare #EmpName varchar(25) = 'Tom'
;with cte(Sort, P1, P2, [path]) as
(
select 1,
1,
charindex('/', [path]+'/', 1),
[path]
from #T
where emp_name = #EmpName
union all
select Sort+1,
P2+1,
charindex('/', [path]+'/', C.P2+1),
[path]
from cte as C
where charindex('/', [path]+'/', C.P2+1) > 0
)
select substring([path], P1, P2-P1)
from cte
order by Sort
Result:
(No column name)
Albert
John
Tom
Test the query here: https://data.stackexchange.com/stackoverflow/q/101383/
Another thing you can try
select T2.emp_name
from #T as T1
inner join #T as T2
on '/'+T1.[path]+'/' like '%/'+T2.emp_name+'/%' and
T2.emp_name <> #EmpName
where T1.emp_name = #EmpName
https://data.stackexchange.com/stackoverflow/q/101518/get-hierarchy-with-join-using-like

Updating records with their subordinates via CTE or subquery

Let's say I have a table with the following columns:
Employees Table
employeeID int
employeeName varchar(50)
managerID int
totalOrganization int
managerID is referential to employeeID. totalOrganization is currently 0 for all records.
I'd like to update totalOrganization on each row to the total number of employees under them.
So with the following records:
employeeID employeeName managerID totalOrganization
1 John Cruz NULL 0
2 Mark Russell 1 0
3 Alice Johnson 1 0
4 Juan Valdez 3 0
The query should update the totalOrganizations to:
employeeID employeeName managerID totalOrganization
1 John Cruz NULL 3
2 Mark Russell 1 0
3 Alice Johnson 1 1
4 Juan Valdez 3 0
I know I can get somewhat of an org. chart using the following CTE:
WITH OrgChart (employeeID, employeeName,managerID,level)
AS (
SELECT employeeID,employeeName,0 as managerID,0 AS Level
FROM Employees
WHERE managerID IS NULL
UNION ALL
SELECT Employees.employeeID,Employees.employeeName,Employees.managerID,Level + 1
FROM Employees INNER JOIN
OrgChart ON Employees.managerID = OrgChart.employeeID
)
SELECT employeeID,employeeName,managerID, level
FROM OrgChart;
Is there any way to update the Employees table using a stored procedure rather than building some routine outside of SQL to parse through the data?
After a few hours of experimentation I came up with the following. It gives the desired results. Anyone see a way to improve it?
CREATE TABLE #totalOrganization (employeeID int,managerID int,level int);
CREATE TABLE #countedOrganization (employeeID int,managerID int,orgCount int,level int);
WITH OrgChart (employeeID,managerID,level)
AS (
SELECT employeeID,0 as managerID,0 AS Level
FROM Emp
WHERE managerID IS NULL
UNION ALL
SELECT Emp.employeeID,Emp.managerID,Level + 1
FROM Emp
INNER JOIN OrgChart
ON Emp.managerID = OrgChart.employeeID
)
INSERT INTO
#totalOrganization
SELECT
employeeID,managerID,level
FROM
OrgChart;
DECLARE #maxLevel int
SELECT
#maxLevel = MAX(level)
FROM
#totalOrganization;
WHILE (#maxLevel > -1)
BEGIN
INSERT INTO
#countedOrganization
SELECT
upline.employeeID,upline.managerID,SUM(CONVERT(INT,CASE WHEN downline.orgCount IS NULL THEN 0 ELSE downline.orgCount END)) + CONVERT(INT,CASE WHEN COUNT(downline.employeeID) IS NULL THEN 0 ELSE COUNT(downline.employeeID) END),upline.level
FROM
#totalOrganization AS upline LEFT OUTER JOIN
#countedOrganization AS downline ON downline.managerID=upline.employeeID
WHERE
upline.level = #maxLevel
GROUP BY
upline.employeeID,upline.managerID,upline.level
SET #maxLevel = #maxLevel - 1
END
UPDATE
Emp
SET
totalOrg= CONVERT(INT,CASE WHEN orgCount IS NULL THEN 0 ELSE orgCount END)
FROM
#countedOrganization INNER JOIN
Emp ON #countedOrganization.employeeID=Emp.employeeID
You can add another CTE to determine the count of employees and then use that in an Update statement:
WITH OrgChart (employeeID, employeeName,managerID,level)
AS (
SELECT employeeID,employeeName,0 as managerID,0 AS Level
FROM Employees
WHERE managerID IS NULL
UNION ALL
SELECT Employees.employeeID,Employees.employeeName,Employees.managerID,Level + 1
FROM Employees
INNER JOIN OrgChart
ON Employees.managerID = OrgChart.employeeID
)
, SubordinateCount As
(
Select ManagerId, Count(*) As Total
From OrgChart
Group By ManagerId
)
Update Employees
Set TotalOrganization = SubordinateCount.Total
FROM SubordinateCount
Join Employees As E
On E.employeeId = SubordinateCount.ManagerId
ADDITION
The change in spec is that you want a count of all subordinate employees. The trick to that is to create a path of the employee to each of their managers. So, first here is my test data:
Insert Employees(EmployeeId, Name, ManagerId) Values(1, 'Alice', Null)
Insert Employees(EmployeeId, Name, ManagerId) Values(2, 'Bob', 1)
Insert Employees(EmployeeId, Name, ManagerId) Values(3, 'Charlie', 1)
Insert Employees(EmployeeId, Name, ManagerId) Values(4, 'Dan', 3)
Insert Employees(EmployeeId, Name, ManagerId) Values(5, 'Ellen', 3)
Insert Employees(EmployeeId, Name, ManagerId) Values(6, 'Fred', 5)
Insert Employees(EmployeeId, Name, ManagerId) Values(7, 'Gale', 6)
Insert Employees(EmployeeId, Name, ManagerId) Values(8, 'Harry', 6)
So, first we write a query that gives us a path to their manager:
With
OrgChart As
(
Select E.EmployeeId, E.Name, Null As ManagerId, 0 AS Level
, Cast( '/' + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100) ) As Path
From dbo.Employees As E
Where E.ManagerId Is Null
Union All
Select E.EmployeeID, E.Name, E.ManagerID, Level + 1
, Cast( OrgChart.Path + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100))
From dbo.Employees As E
Join OrgChart
On OrgChart.EmployeeId = E.ManagerID
)
Select *
From OrgChart
That produces:
EmployeeId Name ManagerId Level Path
1 Alice NULL 0 /1/
2 Bob 1 1 /1/2/
3 Charlie 1 1 /1/3/
4 Dan 3 2 /1/3/4/
5 Ellen 3 2 /1/3/5/
6 Fred 5 3 /1/3/5/6/
7 Gale 6 4 /1/3/5/6/7/
8 Harry 6 4 /1/3/5/6/8/
Now we simply need to count instances where the given employee exists in someone's path:
With
OrgChart As
(
Select E.EmployeeId, E.Name, Null As ManagerId, 0 AS Level
, Cast( '/' + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100) ) As Path
From dbo.Employees As E
Where E.ManagerId Is Null
Union All
Select E.EmployeeID, E.Name, E.ManagerID, Level + 1
, Cast( OrgChart.Path + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100))
From dbo.Employees As E
Join OrgChart
On OrgChart.EmployeeId = E.ManagerID
)
, OrgCounts As
(
Select O.EmployeeId, O.Name, O.ManagerId, O.Level, O.Path
, (Select Count(*)
From OrgChart As O1
Where O1.Path Like '%/' + Cast(E.EmployeeId As varchar(10)) + '/%') - 1 As SubordinateTotal
From Employees As E
Join OrgChart As O
On O.EmployeeId = E.EmployeeId
)
Select O.EmployeeId, O.Name, O.ManagerId, O.Level, O.Path, O.SubordinateTotal
From OrgCounts
I subtract one from the total to exclude the current employee. Now that we've found a query to provide the proper results, we can easily use that to do an update:
With
OrgChart As
(
Select E.EmployeeId, E.Name, Null As ManagerId, 0 AS Level
, Cast( '/' + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100) ) As Path
From dbo.Employees As E
Where E.ManagerId Is Null
Union All
Select E.EmployeeID, E.Name, E.ManagerID, Level + 1
, Cast( OrgChart.Path + Cast(E.EmployeeId As varchar(10)) + '/' As varchar(100))
From dbo.Employees As E
Join OrgChart
On OrgChart.EmployeeId = E.ManagerID
)
, OrgCounts As
(
Select O.EmployeeId, O.Name, O.ManagerId, O.Level, O.Path
, (Select Count(*)
From OrgChart As O1
Where O1.Path Like '%/' + Cast(E.EmployeeId As varchar(10)) + '/%') - 1 As SubordinateTotal
From Employees As E
Join OrgChart As O
On O.EmployeeId = E.EmployeeId
)
Update Employees
Set TotalOrganization = O.SubordinateTotal
From OrgCounts As O
Join dbo.Employees As E
On E.EmployeeId = O.EmployeeId
This can (of course) be done within a stored procedure. However, it looks very much like it cannot be done with a single (CTE) statement, as you cannot sum a given employee's subordinates + all of their subordinates (i.e. tally all descendants underneath a given item in the hierarchy), as per this error message:
GROUP BY, HAVING, or aggregate functions are not allowed in the recursive part of a recursive common table expression 'Subordinates'.
So that routine you'd write outside of SQL (start at the lowest "level" of the hierarchy, count all those employees subordinates, repeat as you iterate up the hierarchy) would have to be written within SQL.