How to transform select query - sql

I have DDL:
drop table names
drop table salary
create table names(
id int,
name1 varchar(50),
char1 varchar(50),
char2 varchar(50))
insert into names values (1,'name1','char1','chara'),
(2,'name2','char2','charb'),
(3,'name3','char3','charc'),
(4,'name4','char4','chard'),
(5,'name5','char5','charf');
create table salary(
id int,
salary int,
bonus int,
oldsalary int)
insert into salary values (1,500,245,354),
(2,600,345,246),
(3,60,365,334),
(4,55,545,364),
(5,25,345,374);
And have the many SQL query' s (one of them)
SELECT n.name1,
s.salary,
s.bonus,
( s.bonus + s.salary ) AS Sumsalary
FROM names n
INNER JOIN salary s
ON n.id = s.id
I want to write select query that retrieves all fieldnames from tables that used in SQL query divided by usage in this select query with some additional info mentioned in screenshot.
Format of data that I want retrieve from this query:

You could get the list of tables & columns for queries if you have access to the system views:
;WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS ns)
SELECT DISTINCT st.TEXT AS QueryText,
C.value('./#Table', 'varchar(50)') As Tab,
C.value('./#Column', 'varchar(50)') As Col
FROM sys.dm_exec_cached_plans AS cp
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS st
CROSS APPLY query_plan.nodes('//ns:ColumnReference') as T(C)
WHERE cp.ObjType = 'Adhoc' AND
St.Text Like '%s.bonus%' AND
st.Text Not Like '%WITH XMLNAMESPACES%' AND
C.value('./#Table', 'varchar(50)') IS NOT NULL
This gives the following results:
QueryText Tab Col
SELECT n.name1, ... [Names] id
SELECT n.name1, ... [Names] name1
SELECT n.name1, ... [Salary] bonus
SELECT n.name1, ... [Salary] id
SELECT n.name1, ... [Salary] salary
You could then use these results as a means of linking to sys.columns to determine which coulmns are missing etc.

Related

Join a table with comma-separated varchar values with another table's Id values in SQL Server

I have two tables, let's call them Users and Fruit.
Users
ID Name Fruit
-------------------
1 Bob 1,3
2 Jack 3
Fruit
ID Name
-------------
1 Apple
2 Orange
3 Grape
How does one join those two tables to fill a datatable with the users choice of fruit names?
Would I need to write a stored procedure with a loop?
I am rather new to SQL Server and would be glad for any help or to be pointed in the right direction.
Created Physical tables with sample data
CREATE TABLE TempUsers
( ID INT,
Name VARCHAR(100),
Fruit VARCHAR(100)
)
INSERT INTO TempUsers
SELECT 1,'Bob' ,'1,3' UNION ALL
SELECT 2,'Jack','3'
CREATE TABLE TempFruit
( ID INT,
Name VARCHAR(100))
INSERT INTO TempFruit
SELECT 1,'Apple' UNION ALL
SELECT 2,'Orange'UNION ALL
SELECT 3,'Grape'
Create A Table-valued-Function to retrive the fruit names as comma separated
CREATE FUNCTION [dbo].[udf_GetFruitNames]
(
#vc_String nvarchar(max)
)
RETURNS #OutTable TABLE
(
Reqdata nvarchar(max)
)
AS
BEGIN
DECLARE #Temp AS TABLE
(
DATA nvarchar(max)
)
INSERT INTO #Temp
SELECT #vc_String;
DECLARE #Temp1 AS TABLE
(
DATA nvarchar(max)
)
INSERT INTO #Temp1
SELECT
STUFF((SELECT DISTINCT ','+ Name FROM
(
SELECT ID,
Name
FROm TempFruit
WHERE ID IN ( SELECT
CAST(Split.a.value('.', 'nvarchar(1000)') AS INT) AS FruitId
FROM
( SELECT
CAST( '<S>'+ REPLACE(DATA,',','</S><S>')+'</S>' AS XML) AS FruitId
FROM #Temp f
)AS A
CROSS APPLY FruitId.nodes('S') AS Split(a))
) As dt FOR XML PATH ('')),1,1,'') As FruitName
INSERT INTO #OutTable
SELECT * FROM #Temp1
RETURN
END
Sql query
SELECT ID
,Name
,uf.Reqdata AS FruitNames
FROM TempUsers u
CROSS APPLY [dbo].[udf_GetFruitNames](u.Fruit) AS uf
Or
SELECT ID
,Name
,(SELECT Reqdata FROM [dbo].[udf_GetFruitNames](u.Fruit) ) AS FruitNames
FROM TempUsers u
Result
ID Name FruitNames
---------------------
1 Bob Apple,Grape
2 Jack Grape
First of all, you need to redesign your tables. There is need for junction table, which will hold which user is connected to what fruit. It is a N:N raletionship.
So, you should create such table:
FruitUser
UserId FruitId
1 1
1 3
2 3
UserId is FK to Users table, FruitId is FK to Fruits table and both of these columns form a compoiste primary key. That's a standard approach.
Then you can use simple join to get results:
select * from users u
join FruitUser fu on u.id = fu.userid
join Fruit f on f.id = fu.fruitId
Sample Data
DECLARE #Users AS TABLE(ID INt, Name VARCHAR(100),fruit VARCHAR(100))
INSERT INTO #Users
SELECT 1,'Bob' ,'1,3' UNION ALL
SELECT 2,'Jack','3'
DECLARE #Fruit AS TABLE(ID INt, Name VARCHAR(100))
INSERT INTO #Fruit
SELECT 1,'Apple' UNION ALL
SELECT 2,'Orange'UNION ALL
SELECT 3,'Grape'
Sql Script
;WITH CTE
AS
(
SELECT UserId,
UserName ,
CAST(Split.a.value('.', 'nvarchar(1000)') AS INT) AS FruitId
FROM
( SELECT u.ID AS UserId,
u.Name AS UserName ,
CAST( '<S>'+ REPLACE(fruit,',','</S><S>')+'</S>' AS XML) AS FruitId
FROM #Fruit f
INNER JOIN #Users u
ON u.ID=f.ID
)AS A
CROSS APPLY FruitId.nodes('S') AS Split(a)
)
SELECT Userid,
UserName,
FruitId,
ft.name AS FruitName
FROM CTE c
LEFT JOIN (SELECT * FROM #Fruit) AS Ft
ON ft.ID=c.FruitId
Result
Userid UserName FruitId FruitName
------------------------------------------
1 Bob 1 Apple
1 Bob 3 Grape
2 Jack 3 Grape
For SQL Server 2014 where you can't use STRING_SPLIT , you can split the varchar using XML like following.
;WITH cte
AS (SELECT id,
name,
fruitid
FROM (SELECT *,
Cast('<X>' + Replace(F.fruit, ',', '</X><X>')
+ '</X>' AS XML) AS xmlfilter
FROM users F)F1
CROSS apply (SELECT fdata.d.value('.', 'varchar(50)') AS FruitId
FROM f1.xmlfilter.nodes('X') AS fdata(d)) O)
SELECT *
FROM cte C
INNER JOIN fruit F
ON F.id = Cast(C.fruitid AS INT)
DEMO
You can achieve the desire result without changing anything with this query
SELECT u.Id, u.Name, f.name
FROM Users u
inner join Fruit f on f.ID IN (SELECT cast(value as int)FROM STRING_SPLIT(u.fruit, ','));
Since you have sql server 2014 you have various options like CLR, XML and number functions. Best one is CLR but it's complex. So you can use this XML code.
select * from
(SELECT ID, [name],LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS fruitid
FROM
(SELECT ID,[name],CAST('<XMLRoot><RowData>' + REPLACE(fruit,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x FROM {User table})t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)) u
inner join {fruit table} f on f.id = u.fruitid

SQL Insert row when a cell does not contain a specific set of characters

My database has a column named Group.
This Group can be one of two values:
Group101 = Main group
Group101D1 = Subgroup
Every group has two options like this.
But I have some situations where Group101D1 exists but Group101 does not.
Now I want to create an insert where I search for groups with D1 that doesn't have a main group. for example I have Group105D1 but don't have Group105. I want an insert to create a row with Group105.
This is as far as I have come:
INSERT INTO (Group)
SELECT [Table1].[Group], [Table2].[Group]
FROM [Table] Table1
INNER JOIN [Table] Table2 ON [table1].[Group] = [Table2].[Group]
-- WHERE [Table2].[Group] LIKE '%D1'
-- AND [Table1].[Group] NOT LIKE '%D1'
Can some of you please help, I don't know how to finish this.
I know I probably need to use inner join, replace and a where not clause.
You will need replace Also query should exclude rows already exists
Sample data:
DECLARE #groups TABLE ([Group] VARCHAR(100), description VARCHAR(100))
INSERT #groups ([Group], description)
SELECT 'Group101', 'Something1'
UNION ALL
SELECT 'Group101d1', 'Something1'
UNION ALL
SELECT 'Group105d1', 'description_Bleh'
UNION ALL
SELECT 'Group2054', 'desc_2054'
UNION ALL
SELECT 'Group2054d1', 'desc_2054'
Use replace function and exclude Maingroup if it exists
SELECT Replace([group], 'd1', '') [Group], description
FROM #groups
WHERE [Group] NOT IN (
SELECT p.[Group]
FROM #groups g
INNER JOIN #groups p
ON g.[Group] = replace(p.[Group], 'd1', '')
)
Result:
Group description
Group105 description_Bleh
If all subgroups ends with "D1" you can use below query to insert missing main groups.
INSERT INTO (Group)
SELECT
left(subtable.Group,(len(subtable.Group)-len('D1')))
FROM
[Table1].[Group] subtable
where
charindex ( 'D1', subtable.Group) > 0 -- if it is sub-record
and
not exists --check if main group exists
(SELECT
1
FROM
[Table1].[Group] main
where (charindex ( main.Group+'D1', subtable.Group) != 0)
)
Stuff you have already just packaged with data
DECLARE #groups TABLE (grp VARCHAR(100), description VARCHAR(100));
INSERT #groups (grp, description) values
('Group101', 'Something1')
, ('Group101d1', 'Something1')
, ('Group105d1', 'description_Bleh')
, ('Group106d1', 'description_Bleh6')
, ('Group2054', 'desc_2054')
, ('Group2054d1', 'desc_2054');
select * from #groups order by grp;
insert into #groups
select replace(g1.grp, 'd1', ''), g1.description
from #groups g1
where g1.grp like '%d1'
and not exists ( select 1
from #groups g2
where g2.Grp = replace(g1.grp, 'd1', '')
);
select * from #groups order by grp;
You can use NOT EXISTS and REPLACE to get the desired results like below :
INSERT INTO Table2(Group)
select replace([Group], 'd1', '') from Table1 a
where [Group] like '%d1' and
not exists(
select 1 from Table1 where [group] = replace(a.[Group], 'd1', '')
)
I did it this way in the end!
insert into Table (Group, Description)
select replace(Group,'D1','') , description from Table
where Group like '%D1'
and replace(Group,'D1','') not in (
select Group from Table
where Group like 'Group%' and Group not like '%D1')

SQL Server stored procedure store 2 table variable in 1 table with from and to values

I have this table stored in variable #oldValues and #newValues:
The two tables above will contain 1 row maximum. My goal is to insert this to a new table JSON TABLE:
DECLARE #jsonTable TABLE
(
[Field] nvarchar(max),
[From] nvarchar(max),
[To] nvarchar(max)
);
and store the from to values from old and new variable
Output must be like this:
[Field] [From] [To] // this is a column name
------------------------------------
CommitteeID 1 1
CommitteeName Test Test2
CommitteeMemberID 1 3
How can I achieve that?
Thanks in advance
It can be plain
select 'CommitteeId' [Field], (select cast(CommitteeId as varchar(max)) from #oldValues) [From], (select cast(CommitteeId as varchar(max)) from #newValues)[To]
union all
select 'CommitteeName', (select CommitteeName from #oldValues), (select CommitteeName from #newValues)
union all
select 'CommitteeId', (select cast(CommitteeMemberId as varchar(max)) from #oldValues), (select cast(CommitteeMemberId as varchar(max)) from #newValues)
If you have only one row:
select v.*
from #oldValues ov cross join
#newValues nv outer apply
(values ('CommitteeId', ov.CommitteeId, nv.CommitteeId),
('CommitteeName', ov.CommitteeName, nv.CommitteeName),
('CommitteeMemberID', ov.CommitteeMemberID, nv.CommitteeMemberID)
) v(field, [from], [to]);
Note: This assumes that the types for the values are all compatible. Otherwise, you may need to convert/cast values to strings.
EDIT:
To be explicit, the casts are:
select v.*
from #oldValues ov cross join
#newValues nv outer apply
(values ('CommitteeId', cast(ov.CommitteeId as nvarchar(255)), cast(nv.CommitteeId as nvarchar(255))),
('CommitteeName', cast(ov.CommitteeName as nvarchar(255)), cast(nv.CommitteeName as nvarchar(255))),
('CommitteeMemberID', cast(ov.CommitteeMemberID as nvarchar(255)), cast(nv.CommitteeMemberID as nvarchar(255)))
) v(field, [from], [to]);
I think UNPIVOT operator most proper solution for the need.
For UNPIVOT operation all column types should be same, that's why we cast all column type to the same.
DECLARE #oldValues as TABLE (CommitteeID INT, CommitteeName VARCHAR(20), CommitteeMemberID INT)
INSERT INTO #oldValues VALUES (1,'Test',1)
DECLARE #newValues as TABLE (CommitteeID INT, CommitteeName VARCHAR(20), CommitteeMemberID INT)
INSERT INTO #newValues VALUES (1,'Test2',3)
DECLARE #jsonTable TABLE
(
[Field] nvarchar(max),
[From] nvarchar(max),
[To] nvarchar(max)
);
;WITH FromTable AS (
SELECT [Field] , [From]
FROM (SELECT CAST(CommitteeID AS VARCHAR(255)) CommitteeID,
CAST(CommitteeName AS VARCHAR(255)) CommitteeName,
CAST(CommitteeMemberID AS VARCHAR(255)) CommitteeMemberID
FROM #oldValues) p
UNPIVOT ( [From] FOR [Field]
IN ( CommitteeID , CommitteeName , CommitteeMemberID)) as UNPVT
)
, ToTable AS (
SELECT [Field] , [To]
FROM (SELECT CAST(CommitteeID AS VARCHAR(255)) CommitteeID,
CAST(CommitteeName AS VARCHAR(255)) CommitteeName,
CAST(CommitteeMemberID AS VARCHAR(255)) CommitteeMemberID
FROM #newValues) p
UNPIVOT ( [To] FOR [Field]
IN ( CommitteeID , CommitteeName , CommitteeMemberID)) as UNPVT
)
SELECT F.*, T.[To] FROM FromTable F FULL JOIN ToTable T ON F.[Field] = T.[Field]
New columns can be easily added to SELECT and IN part of the query.
For easily determine missing column I used FULL JOIN

SQL Server stored procedure looping through a comma delimited cell

I am trying to figure out how to go about getting the values of a comma separated string that's present in one of my cells.
This is the query I current am trying to figure out in my stored procedure:
SELECT
uT.id,
uT.permissions
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
/*Need to loop here I think?*/
WHERE
uT.active = 'true'
AND
uT.email = 'bbarker#thepriceisright.com'
The usersPermissions table looks like this:
And so a row in the usersTbl table looks like this for permissions:
1,3
I need to find a way to loop through that cell and get each number and place the name ****, in my returned results for the usersTbl.permissions.
So instead of returning this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | 1,3 | 87 |
It needs to returns this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | Read,Upload | 87 |
Really just replacing 1,3 with Read,Upload.
Any help would be great from a SQL GURU!
Reworked query
SELECT
*
FROM
usersTbl AS uT
INNER JOIN
usersPermissionsTbl AS uPT
ON
uPT.userId = uT.id
INNER JOIN
usersPermissions AS uP
ON
uPT.permissionId = uP.id
WHERE
uT.active='true'
AND
uT.email='bBarker#thepriceisright.com'
I agree with all of the comments... but strictly trying to do what you want, here's a way with a splitter function
declare #usersTbl table ([Name] varchar(64), id int, [permissions] varchar(64), age int)
insert into #usersTbl
values
('Bbarker',5987,'1,3',87)
declare #usersTblpermissions table (id int, [type] varchar(64))
insert into #usersTblpermissions
values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
;with cte as(
select
u.[Name]
,u.id as UID
,p.id
,p.type
,u.age
from #usersTbl u
cross apply dbo.DelimitedSplit8K([permissions],',') x
inner join #usersTblpermissions p on p.id = x.Item)
select distinct
[Name]
,UID
,age
,STUFF((
SELECT ',' + t2.type
FROM cte t2
WHERE t.UID = t2.UID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from cte t
Jeff Moden Splitter
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
First, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!
Second, you should add a table for user permissions since this is clearly a many to many relationship.
Your tables might look something like this (pseudo code):
usersTbl
(
Id int primary key
-- other user related columns
)
usersPermissionsTbl
(
UserId int, -- Foreign key to usersTbl
PermissionId int, -- Foreign key to permissionsTbl
Primary key (UserId, PermissionId)
)
permissionsTbl
(
Id int primary key,
Name varchar(20)
)
Once you have your tables correct, it's quite easy to get a list of comma separated values from the permissions table.
Adapting scsimon's sample data script to a correct many to many relationship:
declare #users table ([Name] varchar(64), id int, age int)
insert into #users values
('Bbarker',5987,87)
declare #permissions table (id int, [type] varchar(64))
insert into #permissions values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
declare #usersPermissions as table (userId int, permissionId int)
insert into #usersPermissions values (5987, 1), (5987, 3)
Now the query looks like this:
SELECT u.Name,
u.Id,
STUFF(
(
SELECT ','+ [type]
FROM #permissions p
INNER JOIN #usersPermissions up ON p.id = up.permissionId
WHERE up.userId = u.Id
FOR XML PATH('')
)
, 1, 1, '') As Permissions,
u.Age
FROM #Users As u
And the results:
Name Id Permissions Age
Bbarker 5987 Read,Upload 87
You can see a live demo on rextester.
I concur with much of the advice being presented to you in the other responses. The structure you're starting with is not going to be fun to maintain and work with. However, your situation may mean you are stuck with it so maybe some of the tools below will help you.
You can parse the delimiter with charindex() as others demonstrated here- MSSQL - How to split a string using a comma as a separator
... and even better here (several functions are provided) - Split function equivalent in T-SQL?
If you still want to do it with raw inline SQL and are committed to a loop, then pair the string manipulation with a CURSOR. Cursors have their own controversies BTW. The code below will work if your permission syntax remains consistent, which it probably doesn't.
They used charindex(',',columnName) and fed the location into the left() and right() functions along with some additional string evaluation to pull values out. You should be able to piece those together with a cursor
Your query might look like this...
--creating my temp structure
declare #userPermissions table (id int, [type] varchar(16))
insert into #userPermissions (id, [type]) values (1, 'Read')
insert into #userPermissions (id, [type]) values (2, 'Write')
insert into #userPermissions (id, [type]) values (3, 'Upload')
insert into #userPermissions (id, [type]) values (4, 'Admin')
declare #usersTbl table ([Name] varchar(16), id int, [permissions] varchar(8), age int)
insert into #usersTbl ([Name], id, [permissions], age) values ('Bbarker', 5987, '1,3', 87)
insert into #usersTbl ([Name], id, [permissions], age) values ('Mmouse', 5988, '2,4', 88)
--example query
select
ut.[Name]
, (select [type] from #userPermissions where [id] = left(ut.[permissions], charindex(',', ut.[permissions])-1) )
+ ','
+ (select [type] from #userPermissions where [id] = right(ut.[permissions], len(ut.[permissions])-charindex(',', ut.[permissions])) )
from #usersTbl ut

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