Flattening 1-to-many relationship - sql

My current schema looks like this:
PersonType (PersonTypeID, Name, Description)
Person (PersonID,PersonTypeID, FirstName, ... )
PersonDynamicField (PersonDynamicFieldID, PersonTypeID, Name, Description, DataType, DefaultValue, ...)
PersonDynamicFieldValue (PersonDynamicFieldValueID, PersonDynamicFieldID, PersonID, Value, ...)
That is, a person is of a certain type. For example, Customer. For each PersonType, there can dynamically be added additional fields to store about a PersonType. For a Customer, we might want to add fields to PersonDynamicField such as LikesChocolate, FavoriteColor, HappinessScale, etc. The value for those fields would then be stored in PersonDynamicFieldValue.
I hope my writing makes sense.
What I would like to do, is a query that can flatten this structure and return a result looking like this:
PersonID, PersonTypeID, FirstName, LikesChocolate, FavoriteColor, HappinessScale
1, 2, Robert, 1, Green, 9
2, 2, John, 0, Orange, 5
...
I'm kind of stuck and don't really know where to even start.
Can you help?

In order to get the result that you want there are several ways that you can convert the rows of data into columns.
Starting in SQL Server 2005, you can use the PIVOT function. The basic structure of the code will be:
SELECT personid, persontypeid, firstname,[FavoriteColor],[HappinessScale],[LikesChocolate]
from
(
select p.personid, p.persontypeid, p.firstname, f.name fields, v.value
from person p
inner join persontype pt
on p.persontypeid = pt.persontypeid
left join PersonDynamicField f
on p.PersonTypeID = f.PersonTypeID
left join PersonDynamicFieldValue v
on f.PersonDynamicFieldID = v.PersonDynamicFieldID
and p.personid = v.personid
) x
pivot
(
max(value)
for fields in ([FavoriteColor],[HappinessScale],[LikesChocolate])
) p;
See SQL Fiddle with Demo. The one issue that you are going to have with PIVOT is that it requires that the values being converted to columns are known at run-time. For your situation, this seems impossible since the values can change. As a result, you will have to use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(Name)
from PersonDynamicField
where PersonTypeID = 2
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT personid, persontypeid, firstname,' + #cols + '
from
(
select p.personid,
p.persontypeid,
p.firstname,
f.name fields,
v.value
from person p
inner join persontype pt
on p.persontypeid = pt.persontypeid
left join PersonDynamicField f
on p.PersonTypeID = f.PersonTypeID
left join PersonDynamicFieldValue v
on f.PersonDynamicFieldID = v.PersonDynamicFieldID
and p.personid = v.personid
) x
pivot
(
max(value)
for fields in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. These will give a result:
| PERSONID | PERSONTYPEID | FIRSTNAME | FAVORITECOLOR | HAPPINESSSCALE | LIKESCHOCOLATE |
-----------------------------------------------------------------------------------------
| 1 | 2 | Robert | Green | 9 | 1 |
| 2 | 2 | John | Orange | 5 | 0 |

What you want is commonly called a pivot and SQL Server has an operation that may help. Take a look at this article on MSDN for examples: http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx

Related

Check exist multiple value from table

I am trying to get data from table by checking some conditions:
Table Detail
CODE | PRODUCT |
FWD 4X4 | PROD1 |
Table Header
CODE | GROUP |
FWD | AAA |
4X4 | AAA |
FWD | CCC |
Expected Result
CODE | PRODUCT | GROUP |
FWD 4X4 | PROD1 | AAA |
Because group AAA have two codes: FWD & 4X4. Group CCC not qualified because only have one code.
Is it possible to do this by SQL query? I've tried with split string and cross apply not even close though.
Maybe I will use programming language if it's too complex. Since I am not really good with SQL.
Code combination maybe become longer too (3 words or more).
Thanks in advance.
Use String_agg function if your sql server version is 17
select a.code,a.prod,a.group from tabledetail a
inner join
(SELECT [group], STRING_AGG (code, ' ') as c
FROM tableheader
GROUP BY [group]) b on a.code=b.c
for lower version you can use stuff function:
select a.code,a.prod,a.group from tabledetail a
inner join
(SELECT [group], c= STUFF(
(SELECT ' ' + code
FROM tableheader t1
WHERE t1.id = t2.id
FOR XML PATH (''))
, 1, 1, '') from tableheader t2
group by [group])b on a.code=b.c
build the schema
create table Detail (CODE varchar(300) ,PRODUCT varchar(100) );
insert into Detail values ('FWD 4X4','PROD1');
insert into Detail values ('FWD','PROD2');
insert into Detail values ('FWD 4X4 FM','PROD3');
create table Header (CODE varchar(300) ,[GROUP] varchar(100) );
insert into Header values ('FWD','AAA');
insert into Header values ('4X4','AAA');
insert into Header values ('FWD','CCC');
insert into Header values ('4X4','DDD');
insert into Header values ('FM','DDD');
insert into Header values ('FWD','DDD');
solution sql
select d.CODE, d.PRODUCT, h.[GROUP]
from Detail d
inner join Header h on CHARINDEX(' ' +h.code+ ' ', ' ' + d.Code + ' ') > 0
inner join (
select [Group],count(Code) GroupCodesCount
from Header
Group By [Group]
) GroupCodes on GroupCodes.[Group] = h.[GROUP]
group by d.CODE, d.PRODUCT, h.[GROUP],GroupCodesCount
having len(d.CODE) - len(replace(d.CODE, ' ', '')) +1 = count(h.code) and count(h.code) = GroupCodesCount
output result
CODE PRODUCT GROUP
FWD PROD2 CCC
FWD 4X4 PROD1 AAA
FWD 4X4 FM PROD3 DDD
I join Detail table with Header when it contains at least one code on the group CHARINDEX(' ' +h.code+ ' ', ' ' + d.Code + ' ') > 0 then join it to count codes of each group view after that I group it by product and group then filter result groups to return groups that match exactly count of group codes having len(d.CODE) - len(replace(d.CODE, ' ', '')) +1 = count(h.code) and count(h.code) = GroupCodesCount
Hope it helps

How to group-concatenate multiple columns?

Assume this table:
PruchaseID | Customer | Product | Method
-----------|----------|----------|--------
1 | John | Computer | Credit
2 | John | Mouse | Cash
3 | Will | Computer | Credit
4 | Will | Mouse | Cash
5 | Will | Speaker | Cash
6 | Todd | Computer | Credit
I want to generate a report on each customer of what they bought, and their payment methods.
But I want that report to be one row per customer, such as:
Customer | Products | Methods
---------|--------------------------|--------------
John | Computer, Mouse | Credit, Cash
Will | Computer, Mouse, Speaker | Credit, Cash
Todd | Computer | Credit
What I've found so far is to group-concatenate using the XML PATH method, such as:
SELECT
p.Customer,
STUFF(
SELECT ', ' + xp.Product
FROM Purchases xp
WHERE xp.Customer = p.Customer
FOR XML PATH('')), 1, 1, '') AS Products,
STUFF(
SELECT ', ' + xp.Method
FROM Purchases xp
WHERE xp.Customer = p.Customer
FOR XML PATH('')), 1, 1, '') AS Methods
FROM Purchases
This gives me the result, but my concern is the speed of this.
At first glance there are three different selects going on here, two would each multiply by the number of rows Purchases has. Eventually this would slow down expenentially.
So, is there a way to do this with better performance?
I want to add even more columns to aggregate, should I do this STUFF() block for every column? That doesn't sound fast enough for me.
Siggestions?
Just an idea:
DECLARE #t TABLE (
Customer VARCHAR(50),
Product VARCHAR(50),
Method VARCHAR(50),
INDEX ix CLUSTERED (Customer)
)
INSERT INTO #t (Customer, Product, Method)
VALUES
('John', 'Computer', 'Credit'),
('John', 'Mouse', 'Cash'),
('Will', 'Computer', 'Credit'),
('Will', 'Mouse', 'Cash'),
('Will', 'Speaker', 'Cash'),
('Todd', 'Computer', 'Credit')
SELECT t.Customer
, STUFF(CAST(x.query('a/text()') AS NVARCHAR(MAX)), 1, 2, '')
, STUFF(CAST(x.query('b/text()') AS NVARCHAR(MAX)), 1, 2, '')
FROM (
SELECT DISTINCT Customer
FROM #t
) t
OUTER APPLY (
SELECT DISTINCT [a] = CASE WHEN id = 'a' THEN ', ' + val END
, [b] = CASE WHEN id = 'b' THEN ', ' + val END
FROM #t t2
CROSS APPLY (
VALUES ('a', t2.Product)
, ('b', t2.Method)
) t3 (id, val)
WHERE t2.Customer = t.Customer
FOR XML PATH(''), TYPE
) t2 (x)
Output:
Customer Product Method
---------- -------------------------- ------------------
John Computer, Mouse Cash, Credit
Todd Computer Credit
Will Computer, Mouse, Speaker Cash, Credit
Another idea with more performance benefits:
IF OBJECT_ID('tempdb.dbo.#EntityValues') IS NOT NULL
DROP TABLE #EntityValues
DECLARE #Values1 VARCHAR(MAX)
, #Values2 VARCHAR(MAX)
SELECT Customer
, Product
, Method
, RowNum = ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY 1/0)
, Values1 = CAST(NULL AS VARCHAR(MAX))
, Values2 = CAST(NULL AS VARCHAR(MAX))
INTO #EntityValues
FROM #t
UPDATE #EntityValues
SET
#Values1 = Values1 =
CASE WHEN RowNum = 1
THEN Product
ELSE #Values1 + ', ' + Product
END
, #Values2 = Values2 =
CASE WHEN RowNum = 1
THEN Method
ELSE #Values2 + ', ' + Method
END
SELECT Customer
, Values1 = MAX(Values1)
, Values2 = MAX(Values2)
FROM #EntityValues
GROUP BY Customer
But with some limitations:
Customer Values1 Values2
------------- ----------------------------- ----------------------
John Computer, Mouse Credit, Cash
Todd Computer Credit
Will Computer, Mouse, Speaker Credit, Cash, Cash
Also check my old post about string aggregation:
http://www.codeproject.com/Articles/691102/String-Aggregation-in-the-World-of-SQL-Server
Another solution is the CLR method for group concatenation #aaron bertrand has done a performance comparison on this here.
If you can deploy CLR then download the script from https://orlando-colamatteo.github.io/ms-sql-server-group-concat-sqlclr/ which is free.
and all details are there in the documentation.
Your query will just change into like this
SELECT Customer,dbo.GROUP_CONCAT(product),dbo.GROUP_CONCAT(method)
FROM Purchases
GROUP BY Customer
This query is short, easy to remember and use, XML method also does the job but remembering the code is a bit difficult(atleast for me) and creeps in the problem like XML entitization which can be solved sure and some pitfalls also described in his blog.
Also from a performance view point using .query is time consuming I had the same issues with performance. I hope you can find this question I raised here in https://dba.stackexchange.com/questions/125771/multiple-column-concatenation
check the version 2 given by kenneth fisher a nested xml concatenation method or a unpivot /pivot method suggested by spaggettidba.
This is one of the use cases for recursive CTEs (Common Table Expressions). You can learn more here https://technet.microsoft.com/en-us/library/ms190766(v=sql.105).aspx
;
WITH CTE1 (PurchaseID, Customer, Product, Method, RowID)
AS
(
SELECT
PurchaseID, Customer, Product, Method,
ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Customer)
FROM
#tbl
/* This table holds source data. I ommited declaring and inserting
data into it because that's not important. */
)
, CTE2 (PurchaseID, Customer, Product, Method, RowID)
AS
(
SELECT
PurchaseID, Customer,
CONVERT(VARCHAR(MAX), Product),
CONVERT(VARCHAR(MAX), Method),
1
FROM
CTE1
WHERE
RowID = 1
UNION ALL
SELECT
CTE2.PurchaseID, CTE2.Customer,
CONVERT(VARCHAR(MAX), CTE2.Product + ',' + CTE1.Product),
CONVERT(VARCHAR(MAX), CTE2.Method + ',' + CTE1.Method),
CTE2.RowID + 1
FROM
CTE2 INNER JOIN CTE1
ON CTE2.Customer = CTE1.Customer
AND CTE2.RowID + 1 = CTE1.RowID
)
SELECT Customer, MAX(Product) AS Products, MAX(Method) AS Methods
FROM CTE2
GROUP BY Customer
Output:
Customer Products Methods
John Computer,Mouse Credit,Cash
Todd Computer Credit
Will Computer,Mouse,Speaker Credit,Cash,Cash

SQL SELECT with JOINS and Multiple Rows for one Column

The issue at hand is that I have a basic query (from a document software database), taking from multiple tables with some LEFT JOINS, but I have one column in question that needs to take from a table where there are multiple results per unique document id (DocGUID).
CURRENT QUERY
SELECT doc.[Doc #]
, '' AS 'Authors'
, ud.[Lead Author]
, doc.[Title]
, ud.[Publication]
, ud.[Citation]
, ud.[Year]
, ud.[Month]
, ud.[Comments]
, notes.[Note]
FROM [tblDocuments] doc
LEFT JOIN [tblNotes] notes ON notes.[DocGUID] = doc.[DocGUID]
LEFT JOIN [tblUserData] ud ON ud.[MasterGUID] = doc.[DocGUID]
WHERE doc.[DocGUID] = '12345678'
As you can see, I have simply queried '' for "Authors". Here's where my issue comes in. I have a table named tblMultiValues where there are two or more authors listed per DocGUID.
Table Example: (for tblMultiValues)
|------|-------------|-------------|-------------------|
| Id | DocGUID | FieldName | Value |
|------|-------------|-------------|-------------------|
| 123 | 12345678 | Authors | Collins, Nick |
| 456 | 12345678 | Authors | Williams, Robert |
| 321 | 87654321 | Authors | Smith, Kate |
| 654 | 87654321 | Authors | Hanks, Tom |
|------|-------------|-------------|-------------------|
So, what I want to show for the 2nd column of 'Authors', is the result of:
Collins, Nick; Williams, Robert
Specifically for DocGUID of '12345678'
How might one go about doing this, mixed in with the query that is already built?
(I hope this was enough info... if more is needed, please advise).
-Nick
:::EDIT:::
I was able to get things running with the following code... (very well guided from the answer given by #mohan111
SELECT DISTINCT
STUFF((
SELECT '; ' + mv2.Value
FROM [dbo].[tblMultiValues] mv2
WHERE mv1.DocGUID = mv2.DocGUID
FOR XML PATH ('')),1,2,'') AS 'Authors', mv1.FieldName, mv1.DocGUID
INTO #TempMultival
FROM [dbo].[tblMultiValues] mv1
SELECT doc.[Doc #]
, tmv.[Authors]
, ud.[Lead Author]
, doc.[Title]
, ud.[Publication]
, ud.[Citation]
, ud.[Year]
, ud.[Month]
, ud.[Comments]
, notes.[Note]
FROM [tblDocuments] doc
LEFT JOIN [tblNotes] notes ON notes.[DocGUID] = doc.[DocGUID]
LEFT JOIN [tblUserData] ud ON ud.[MasterGUID] = doc.[DocGUID]
LEFT JOIN #TempMultiVal tmv ON tmv.DoCGUID = doc.[DocGUID]
DROP TABLE #TempMultiVal
Declare #table TABLE
(
Id INT,
DocGUID int,
FieldName VARCHAR(25),
Value VARCHAR(200)
);
INSERT INTO #table
( Id,
DocGUID,
FieldName,
Value
)
VALUES
(123,12345678,'Authors','Collins, Nick'),
(456,12345678,'Authors','Williams, Robert'),
(321,87654321,'Authors','Smith, Kate'),
(654,87654321,'Authors','Hanks, Tom');
Select distinct DocGUID,
(SELECT
Substring((SELECT ', ' + CAST(i.id AS VARCHAR(1024))
FROM
#table i
WHERE i.DocGUID = tt.DocGUID
ORDER BY i.id
FOR XML PATH('')), 3, 10000000) AS list) AS ID,
FieldName,
STUFF((Select distinct t.Value + ','
from #table t
where t.DocGUID = tt.DocGUID
FOR XML PATH(''),TYPE).value('.', 'NVARCHAR(MAX)')
, 1, 0, ' ') from #table tt

Select query to get all data from junction table to one field

I have 2 tables and 1 junction table:
table 1 (Log): | Id | Title | Date | ...
table 2 (Category): | Id | Title | ...
junction table between table 1 and 2:
LogCategory: | Id | LogId | CategoryId
now, I want a sql query to get all logs with all categories title in one field,
something like this:
LogId, LogTitle, ..., Categories(that contains all category title assigned to this log id)
can any one help me solve this? thanks
Try this code:
DECLARE #results TABLE
(
idLog int,
LogTitle varchar(20),
idCategory int,
CategoryTitle varchar(20)
)
INSERT INTO #results
SELECT l.idLog, l.LogTitle, c.idCategory, c.CategoryTitle
FROM
LogCategory lc
INNER JOIN Log l
ON lc.IdLog = l.IdLog
INNER JOIN Category c
ON lc.IdCategory = c.IdCategory
SELECT DISTINCT
idLog,
LogTitle,
STUFF (
(SELECT ', ' + r1.CategoryTitle
FROM #results r1
WHERE r1.idLog = r2.idLog
ORDER BY r1.idLog
FOR XML PATH ('')
), 1, 2, '')
FROM
#results r2
Here you have a simple SQL Fiddle example
I'm sure this query can be written using only one select, but this way it is readable and I can explain what the code does.
The first select takes all Log - Category matches into a table variable.
The second part uses FOR XML to select the category names and return the result in an XML instead of in a table. by using FOR XML PATH ('') and placing a ', ' in the select, all the XML tags are removed from the result.
And finally, the STUFF instruction replaces the initial ', ' characters of every row and writes an empty string instead, this way the string formatting is correct.

SQL query assistance with bridge table

I'm working with a existing database and trying to write a sql query to get out all the account information including permission levels. This is for a security audit. We want to dump all of this information out in a readible fashion to make it easy to compare. My problem is that there is a bridge/link table for the permissions so there are multiple records per user. I want to get back results with all the permission for one user on one line. Here is an example:
Table_User:
UserId UserName
1 John
2 Joe
3 James
Table_UserPermissions:
UserId PermissionId Rights
1 10 1
1 11 2
1 12 3
2 11 2
2 12 3
3 10 2
PermissionID links to a table with the name of the Permission and what it does. Right is like 1 = view, 2 = modify, and etc.
What I get back from a basic query for User 1 is:
UserId UserName PermissionId Rights
1 John 10 1
1 John 11 2
1 John 12 3
What I would like something like this:
UserId UserName Permission1 Rights1 Permission2 Right2 Permission3 Right3
1 John 10 1 11 2 12 3
Ideally I would like this for all users.
The closest thing I've found is the Pivot function in SQL Server 2005.
Link
The problem with this from what I can tell is that I need to name each column for each user and I'm not sure how to get the rights level. With real data I have about 130 users and 40 different permissions.
Is there another way with just sql that I can do this?
You could do something like this:
select userid, username
, max(case when permissionid=10 then rights end) as permission10_rights
, max(case when permissionid=11 then rights end) as permission11_rights
, max(case when permissionid=12 then rights end) as permission12_rights
from userpermissions
group by userid, username;
You have to explicitly add a similar max(...) column for each permissionid.
If you where using MySQL I would suggest you use group_concat() like below.
select UserId, UserName,
group_concat(PermissionId) as PermIdList,
group_concat(Rights SEPARATOR ',') as RightsList
from Table_user join Table_UserPermissions on
Table_User.UserId = Table_UserPermissions.UserId=
GROUP BY Table_User.UserId
This would return
UserId UserName PermIdList RightsList
1 John 10,11,12 1,2,3
A quick google search for 'mssql group_concat' revealed a couple different stored procedures (I), (II) for MSSQL that can achieve the same behavior.
Short answer:
No.
You can't dynamically add columns in to your query.
Remember, SQL is a set based language. You query sets and join sets together.
What you're digging out is a recursive list and requiring that the list be strung together horizontally rather then vertically.
You can, sorta, fake it, with a set of self joins, but in order to do that, you have to know all possible permissions before you write the query...which is what the other suggestions have proposed.
You can also pull the recordset back into a different language and then iterate through that to generate the proper columns.
Something like:
SELECT Table_User.userID, userName, permissionid, rights
FROM Table_User
LEFT JOIN Table_UserPermissions ON Table_User.userID =Table_UserPermissions.userID
ORDER BY userName
And then display all the permissions for each user using something like (Python):
userID = recordset[0][0]
userName = recordset[0][1]
for row in recordset:
if userID != row[0]:
printUserPermissions(username, user_permissions)
user_permissions = []
username = row[1]
userID = row[0]
user_permissions.append((row[2], row[3]))
printUserPermissions(username, user_permissions)
You could create a temporary table_flatuserpermissions of:
UserID
PermissionID1
Rights1
PermissionID2
Rights2
...etc to as many permission/right combinations as you need
Insert records to this table from Table_user with all permission & rights fields null.
Update records on this table from table_userpermissions - first record insert and set PermissionID1 & Rights1, Second record for a user update PermissionsID2 & Rights2, etc.
Then you query this table to generate your report.
Personally, I'd just stick with the UserId, UserName, PermissionID, Rights columns you have now.
Maybe substitute in some text for PermissionID and Rights instead of the numeric values.
Maybe sort the table by PermissionID, User instead of User, PermissionID so the auditor could check the users on each permission type.
If it's acceptable, a strategy I've used, both for designing and/or implementation, is to dump the query unpivoted into either Excel or Access. Both have much friendlier UIs for pivoting data, and a lot more people are comfortable in that environment.
Once you have a design you like, then it's easier to think about how to duplicate it in TSQL.
It seems like the pivot function was designed for situations where you can use an aggregate function on one of the fields. Like if I wanted to know how much revenue each sales person made for company x. I could sum up the price field from a sales table. I would then get the sales person and how much revenue in sales they have. For the permissions though it doesn't make sense to sum/count/etc up the permissionId field or the Rights field.
You may want to look at the following example on creating cross-tab queries in SQL:
http://www.databasejournal.com/features/mssql/article.php/3521101/Cross-Tab-reports-in-SQL-Server-2005.htm
It looks like there are new operations that were included as part of SQL Server 2005 called PIVOT and UNPIVOT
For this type of data transformation you will need to perform both an UNPIVOT and then a PIVOT of the data. If you know the values that you want to transform, then you can hard-code the query using a static pivot, otherwise you can use dynamic sql.
Create tables:
CREATE TABLE Table_User
([UserId] int, [UserName] varchar(5))
;
INSERT INTO Table_User
([UserId], [UserName])
VALUES
(1, 'John'),
(2, 'Joe'),
(3, 'James')
;
CREATE TABLE Table_UserPermissions
([UserId] int, [PermissionId] int, [Rights] int)
;
INSERT INTO Table_UserPermissions
([UserId], [PermissionId], [Rights])
VALUES
(1, 10, 1),
(1, 11, 2),
(1, 12, 3),
(2, 11, 2),
(2, 12, 3),
(3, 10, 2)
;
Static PIVOT:
select *
from
(
select userid,
username,
value,
col + '_'+ cast(rn as varchar(10)) col
from
(
select u.userid,
u.username,
p.permissionid,
p.rights,
row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) src
unpivot
(
value
for col in (permissionid, rights)
) unpiv
) src
pivot
(
max(value)
for col in (permissionid_1, rights_1,
permissionid_2, rights_2,
permissionid_3, rights_3)
) piv
order by userid
See SQL Fiddle with Demo
Dynamic PIVOT:
If you have an unknown number of permissionids and rights, then you can use dynamic sql:
DECLARE
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name +'_'+ cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) t
cross apply sys.columns as C
where C.object_id = object_id('Table_UserPermissions') and
C.name not in ('UserId')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select userid,
username,
value,
col + ''_''+ cast(rn as varchar(10)) col
from
(
select u.userid,
u.username,
p.permissionid,
p.rights,
row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) src
unpivot
(
value
for col in (permissionid, rights)
) unpiv
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p
order by userid'
exec(#query)
See SQL Fiddle with demo
The result for both is:
| USERID | USERNAME | PERMISSIONID_1 | RIGHTS_1 | PERMISSIONID_2 | RIGHTS_2 | PERMISSIONID_3 | RIGHTS_3 |
---------------------------------------------------------------------------------------------------------
| 1 | John | 10 | 1 | 11 | 2 | 12 | 3 |
| 2 | Joe | 11 | 2 | 12 | 3 | (null) | (null) |
| 3 | James | 10 | 2 | (null) | (null) | (null) | (null) |