Combine the Rows into single row in SQL Server [duplicate] - sql

This question already has answers here:
How to use GROUP BY to concatenate strings in SQL Server?
(22 answers)
Closed 4 years ago.
DECLARE #Count BIGINT
SELECT #Count = Count(ID)
FROM Users;
SELECT TOP 50
CustUser.[ID],
CustUser.[FirstName] + ' ' + CustUser.[LastName] FirstName,
CustUser.[NickName],
CustUser.[UserName],
R.[Name] Roles
FROM
(SELECT
ROW_NUMBER() OVER(ORDER BY US.ID Desc) AS Row,
US.[ID], US.[FirstName], US.[LastName], US.[NickName],
US.[UserName], US.[Password]
FROM
[Users] US) CustUser
LEFT JOIN
Category CL ON CustUser.[LoginModeCode] = CL.CategoryCode
LEFT JOIN
Category CS ON CustUser.[StatusCode] = CS.CategoryCode
LEFT JOIN
UserRoles UR ON UR.UserID = CustUser.ID
LEFT JOIN
Roles R ON R.ID = UR.RoleID
WHERE
CustUser.ID = 3 AND
[Row] > (1 - 1) * 50
ORDER BY
FirstName
This query returns the below output
ID FirstName NickName UserName Roles
----------------------------------------------------------------------------
3 ram jk ram Developer
3 ram jk ram TeamLeader
Roles only different in above rows. I am combining two rows.
But I want this output
ID FirstName NickName UserName Roles
--------------------------------------------------------------
3 ram jk ram Developer, TeamLeader

--Test Data
CREATE TABLE #Temp_table(
ID int,
FirstName nvarchar(200),
NickName nvarchar(200),
UserName nvarchar(200),
Roles nvarchar(200)
);
INSERT INTO #Temp_table VALUES (3,'ram','jk','ram','Developer');
INSERT INTO #Temp_table VALUES (3,'ram','jk','ram','TeamLeader');
INSERT INTO #Temp_table VALUES (3,'ram','jk','ram','XXXLeader');
--change temp_table to your select table
select ID,FirstName,NickName,UserName,
STUFF((
SELECT ', ' + Roles
FROM #Temp_table
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') Roles
from #Temp_table
group by ID,FirstName,NickName,UserName;

Related

SQL - get all parents/childs?

hopefully someone can help with this. I have recieved a table of data which I need to restructure and build a Denorm table out of. The table structure is as follows
UserID Logon ParentID
2344 Test1 2000
2345 Test2 2000
The issue I have is the ParentID is also a UserID of its own and in the same table.
SELECT * FROM tbl where ParentID=2000 gives the below output
UserID Logon ParentID
2000 Test Team 2500
Again, the ParentID of this is also stored as a UserID..
SELECT * FROM tbl where ParentID=2500 gives the below output
UserID Logon ParentID
2500 Test Division NULL
I want a query that will pull all of these relationships and the logons into one row, with my output looking like the below.
UserID Username Parent1 Parent2 Parent3 Parent4
2344 Test1 Test Team Test Division NULL NULL
2345 Test2 Test Team Test Division NULL NULL
The maximum number of parents a user can have is 4, in this case there is only 2. Can someone help me with the query needed to build this?
Appreciate any help
Thanks
Jess
You can use basicly LEFT JOIN. If you have static 4 parent it should work. If you have unknown parents you should do dynamic query.
SELECT U1.UserId
,U1.UserName
,U2.UserName AS Parent1
,U3.UserName AS Parent2
,U4.UserName AS Parent3
,U5.UserName AS Parent4
FROM Users U1
LEFT JOIN Users U2 ON U1.ParentId = U2.UserId
LEFT JOIN Users U3 ON U2.ParentId = U3.UserId
LEFT JOIN Users U4 ON U3.ParentId = U4.UserId
LEFT JOIN Users U5 ON U4.ParentId = U5.UserId
EDIT : Additional(to exclude parent users from the list) :
WHERE NOT EXISTS (SELECT 1 FROM Users UC WHERE U1.UserId = UC.ParentId)
select
tb1.UserId as UserId,
tb1.UserName as UserName,
tb2.UserName as Parent1,
tb3.UserName as Parent2,
tb4.UserName as Parent3,
tb5.UserName as Parent4
from tbl t1
left join tbl t2 on t2.UserId=t1.ParentID
left join tbl t3 on t3.UserId=t2.ParentID
left join tbl t4 on t4.UserId=t3.ParentID
left join tbl t5 on t5.UserId=t4.ParentID;
you need to do 4 left joins in order to fetch 4 parent details
Use a recursive CTE to get the levels then pivot to put them in columns:
WITH cte(UserID, Logon, ParentID, ParentLogon, ParentLevel) AS
(
SELECT UserID, Logon, ParentID, Logon, 0
FROM users
UNION ALL
SELECT u.UserID, u.Logon, u.ParentID, cte.ParentLogon, ParentLevel + 1
FROM users u
JOIN cte ON cte.UserID = u.ParentID
)
SELECT UserId, Logon, Parent1, Parent2, Parent3, Parent4 FROM cte
PIVOT (
MAX(ParentLogon)
FOR ParentLevel
IN (
1 AS Parent1,
2 AS Parent2,
3 AS Parent3,
4 AS Parent4
)
)
See SQL Fiddle example
In order to get all parent or child, it's efficient to use a recursive function which would fetch the whole hierarchy.
Sample Table:
CREATE TABLE #TEST
(
[Name] varchar(100),
ManagerName Varchar(100),
Number int
)
Insert some values
Insert into Test values
('a','b'), ('b','c'), ('c','d'), ('d','e'), ('e','f'), ('f','g')
Create recursive function as below
CREATE FUNCTION [dbo].[fnRecursive] (#EmpName Varchar(100), #incremental int)
RETURNS #ret TABLE
(
ManagerName varchar(100),
Number int
)
AS
BEGIN
Declare #MgrName varchar(100)
SET #MgrName = (Select ManagerName from test where [name] = #EmpName)
Insert into #ret values (#MgrName, #incremental)
if(#MgrName is not null)
BEGIN
SET #incremental = #incremental + 1;
Insert into #ret
Select ManagerName, Number from [fnRecursive](#MgrName, #incremental)
END
RETURN;
END
If this function is joined with table, it should list the hierarchy for all employees
CREATE TABLE #TEST
(
[Name] varchar(100),
ManagerName Varchar(100),
Number int
)
Insert into #TEST
Select x.[Name], x.ManagerName,x.number from (
select t.[Name],a.ManagerName as managerName, a.number as number from TEST t outer apply
(
select * from [fnRecursive](t.[Name],1)
) a)
x
Select * from #Test
If we do a pivot on the table (excluding the 'Number' column). Assuming we store in the table "#temp" it should list all the managers as a column.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.[ManagerName] )
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'select * from #temp
pivot
(
min([managername])
for managername in (' + #cols + ')
) p '
execute(#query)
But this doesn't name the column as 'Parent1', 'Parent2' instead with the dynamic column name.
Link below should help to set custom column name for the dynamic pivot table
https://stackoverflow.com/questions/16614994/sql-server-pivot-with-custom-column-names

TSQL Multiple count using same table with different JOIN

I have a weird situation and not too sure how to approach it.
I have 2 separate tables:
Table A is submissions
id
submitterQID
nomineeQID
story
Table B is employees
QID
Name
Department
I am trying to get the total number of submissions grouped by department as well as the total number of nominations.
This is what my Stored procedure looks like:
BEGIN
SELECT TOP 50 count(A.[nomineeQID]) AS totalNominations,
count(A.[subQID]) AS totalSubmissions,
B.[DepartmentDesc] AS department
FROM empowermentSubmissions AS A
JOIN empTable AS B
ON B.[qid] = A.[nomineeQID]
WHERE A.[statusID] = 3
AND A.[locationID] = #locale
GROUP BY B.[Department]
ORDER BY totalNominations DESC
FOR XML PATH ('data'), TYPE, ELEMENTS, ROOT ('root');
END
This issue with this is that the JOIN is joining by the nomineeQID only and not the subQID as well.
My end result I am looking for is:
Department Customer Service has 25 submissions and 90 nominations
ORDERED BY the SUM of both counts...
I tried to just JOIN again on the subQID but was told I cant join on the same table twice.
Is there an easier way to accomplish this?
This is a situaton where you'll need to gather your counts independently of each other. Using two left joins will cause some rows to be counted twice in the first left join when the join condition is met for both. Your scenario can be solved using either correlated subqueries or an outer apply gathering the counts on different criteria. I did not present a COUNT(CASE ... ) option here, because you don't have an either-or scenario in the data, you have two foreign keys to the employees table. So, setting up sample data:
declare #empowermentSubmissions table (submissionID int primary key identity(1,1), submissionDate datetime, nomineeQID INT, submitterQID INT, statusID INT, locationID INT)
declare #empTable table (QID int primary key identity(1,1), AreaDesc varchar(10), DepartmentDesc varchar(20))
declare #locale INT = 0
declare #n int = 1
while #n < 50
begin
insert into #empTable (AreaDesc, DepartmentDesc) values ('Area ' + cast((#n % 2)+1 as varchar(1)), 'Department ' + cast((#n % 4)+1 as varchar(1)))
set #n = #n + 1
end
set #n = 1
while #n < 500
begin
insert into #empowermentSubmissions (submissionDate, nomineeQID, submitterQID, StatusID, locationID) values (dateadd(dd,-(cast(rand()*600 as int)),getdate()), (select top 1 QID from #empTable order by newid()), (select top 1 QID from #empTable order by newid()), 3 + (#n % 2) - (#n % 3), (#n % 2) )
set #n = #n + 1
end
And now the OUTER APPLY option:
SELECT TOP 50 E.DepartmentDesc, SUM(N.Nominations) Nominations, SUM(S.TotalSubmissions) TotalSubmissions
FROM #empTable E
OUTER APPLY (
SELECT COUNT(submissionID) Nominations
FROM #empowermentSubmissions A
WHERE A.statusID = 3
AND A.nomineeQID = E.QID
AND A.locationID = #locale
) N
OUTER APPLY (
SELECT COUNT(submissionID) TotalSubmissions
FROM #empowermentSubmissions A
WHERE A.statusID = 3
AND A.submitterQID = E.QID
AND A.locationID = #locale
) S
GROUP BY E.DepartmentDesc
ORDER BY SUM(Nominations) + SUM(TotalSubmissions) DESC

sql query help - trying to get rid of temp tables

I have the following tables -
Resource
--------------------
Id, ProjectId, Hours, ApproverId
Project
--------------------
Id, Name
The input is ApproverId. I need to retrieve all the rows that have matching ApproverId (simple enough). And for every resource that I get back, I also need to get their hours (same table) whose approverId is not the one that is passed in (business requirement, to be grayed out in the UI). What I'm doing right now is - get all resources based on ApproverId, stored them in a temp table, then do a distinct on Resource.Id, store it in a different temp table, and then for every Resource.Id, get the rows where the ApproverId is not the one that is passed. Can I combine it all in a single query instead of using temp tables?
Thanks!
Edit: I'm using SQL Server 2008 R2.
Edit 2: Here's my stored procedure. I have changed the logic slightly after reading the comments. Can we get rid of all temp tables and make it faster -
ALTER PROCEDURE GetResourceDataByApprover
#ApproverId UNIQUEIDENTIFIER
AS
CREATE TABLE #Table1
(
Id SMALLINT PRIMARY KEY
IDENTITY(1, 1) ,
ResourceId UNIQUEIDENTIFIER
)
CREATE TABLE #Table2
(
ResourceId UNIQUEIDENTIFIER ,
ProjectId UNIQUEIDENTIFIER ,
ProjectName NVARCHAR(1024)
)
INSERT INTO #Table1
SELECT DISTINCT
ResourceId
FROM dbo.Resource T
WHERE T.ApproverId = #ApproverId
DECLARE #i INT
DECLARE #numrows INT
DECLARE #resourceId UNIQUEIDENTIFIER
SET #i = 1
SET #numrows = ( SELECT COUNT(*)
FROM #Table1
)
IF #numrows > 0
WHILE ( #i <= ( SELECT MAX(Id)
FROM #Table1
) )
BEGIN
SET #resourceId = ( SELECT ResourceId
FROM #Table1
WHERE Id = #i
)
INSERT INTO #Table2
SELECT
T.ResourceId ,
T.ProjectId ,
P.Name AS ProjectName
FROM dbo.[Resource] T
INNER JOIN dbo.Project P ON T.ProjectId = P.ProjectId
WHERE T.ResourceId = #resourceId
SET #i = #i + 1
END
SELECT *
FROM #Table1
SELECT *
FROM #Table2
DROP TABLE #Table1
DROP TABLE #Table2
This query should return two rows for every resource, one for the specified approver and one for all other approvers.
SELECT
Id,
CASE
WHEN ApproverId=#approverId THEN 'SpecifiedApprover'
ELSE 'OtherApprover'
END AS Approver,
SUM(Hours) AS Hours
FROM Resource
GROUP BY
Id,
CASE
WHEN ApproverId=#approverId THEN 'SpecifiedApprover'
ELSE 'OtherApprover'
END
Do you want to know how concrete Approver wastes his time?
SELECT p.Id, p.Name, SUM(r.Hours) as TotalHours
FROM Resource r
LEFT JOIN Project p
ON r.ProjectId = p.Id
WHERE ApproverId = %ConcreteApproverId%
GROUP BY p.Id, p.Name
HAVING SUM(r.Hours) > 0
This query will produce this table example:
+-----+----------+-------+
| Id | Project | Hours |
+-----+----------+-------+
| 203 | ProjectA | 25 |
| 202 | ProjectB | 34 |
| 200 | ProjectC | 46 |
+-----+----------+-------+

Picking Random Names

I saw an interesting post sometime back but with no solution. Trying luck here:
There is a table which contain 10 names (U1, U2, U3..and so on). I have to choose 5 names everyday, and display one as the Editor and 4 as Contributors
While selecting the random names, I have to also consider that if one user is selected as Editor, he cannot become editor again till everyone got their chance.
The output should look similar to the following:
Editor Cont1 Cont2 Cont3 Cont4
20-Jun U1 U8 U9 U3 U4
21-Jun U7 U2 U5 U6 U10
22-Jun U3 U4 U9 U2 U8
23-Jun U4 U8 U3 U5 U2
and so on..
This migth be one way to do it. Most likely, shorter versions are possible but the output seem to match your requirements.
The gist of the solution goes as follows
Add a counter for every user for how many times a user has been an editor and how many times he has been a contributor.
Select one random user from all users with the lowest EditorCount using a TOP 1 and NEWID() and update that user's EditorCount.
Likewise the selection(s) for contributors. Select one random user from all users with the lowest ContributorCount, excluding users who just been made editor/contributor and update that user's ContributeCount.
SQL Script
SET NOCOUNT ON
DECLARE #Users TABLE (
UserName VARCHAR(3)
, EditorCount INTEGER
, ContributorCount INTEGER
)
DECLARE #Solutions TABLE (
ID INTEGER IDENTITY(1, 1)
, Editor VARCHAR(3)
, Contributor1 VARCHAR(3)
, Contributor2 VARCHAR(3)
, Contributor3 VARCHAR(3)
, Contributor4 VARCHAR(3)
)
DECLARE #Editor VARCHAR(3)
DECLARE #Contributor1 VARCHAR(3)
DECLARE #Contributor2 VARCHAR(3)
DECLARE #Contributor3 VARCHAR(3)
DECLARE #Contributor4 VARCHAR(3)
INSERT INTO #Users
SELECT 'U1', 0, 0
UNION ALL SELECT 'U2', 0, 0
UNION ALL SELECT 'U3', 0, 0
UNION ALL SELECT 'U4', 0, 0
UNION ALL SELECT 'U5', 0, 0
UNION ALL SELECT 'U6', 0, 0
UNION ALL SELECT 'U7', 0, 0
UNION ALL SELECT 'U8', 0, 0
UNION ALL SELECT 'U9', 0, 0
UNION ALL SELECT 'U0', 0, 0
/* Keep Generating combinations until at least one user has been editor for 10 times */
WHILE NOT EXISTS (SELECT * FROM #Solutions WHERE ID = 30)
BEGIN
SELECT TOP 1 #Editor = u.UserName
FROM #Users u
INNER JOIN (
SELECT EditorCount = MIN(EditorCount)
FROM #Users
) ec ON ec.EditorCount = u.EditorCount
ORDER BY NEWID()
UPDATE #Users SET EditorCount = EditorCount + 1 WHERE UserName = #Editor
INSERT INTO #Solutions VALUES (#Editor, NULL, NULL, NULL, NULL)
SELECT TOP 1 #Contributor1 = u.UserName
FROM #Users u
INNER JOIN (
SELECT ContributorCount = MIN(ContributorCount)
FROM #Users
) ec ON ec.ContributorCount = u.ContributorCount
WHERE UserName <> #Editor
ORDER BY NEWID()
UPDATE #Users SET ContributorCount = ContributorCount + 1 WHERE UserName = #Contributor1
UPDATE #Solutions SET Contributor1 = #Contributor1 WHERE Contributor1 IS NULL
SELECT TOP 1 #Contributor2 = u.UserName
FROM #Users u
INNER JOIN (
SELECT ContributorCount = MIN(ContributorCount)
FROM #Users
) ec ON ec.ContributorCount = u.ContributorCount
WHERE UserName NOT IN (#Editor, #Contributor1)
ORDER BY NEWID()
UPDATE #Users SET ContributorCount = ContributorCount + 1 WHERE UserName = #Contributor2
UPDATE #Solutions SET Contributor2 = #Contributor2 WHERE Contributor2 IS NULL
SELECT TOP 1 #Contributor3 = u.UserName
FROM #Users u
INNER JOIN (
SELECT ContributorCount = MIN(ContributorCount)
FROM #Users
) ec ON ec.ContributorCount = u.ContributorCount
WHERE UserName NOT IN (#Editor, #Contributor1, #Contributor2)
ORDER BY NEWID()
UPDATE #Users SET ContributorCount = ContributorCount + 1 WHERE UserName = #Contributor3
UPDATE #Solutions SET Contributor3 = #Contributor3 WHERE Contributor3 IS NULL
SELECT TOP 1 #Contributor4 = u.UserName
FROM #Users u
INNER JOIN (
SELECT ContributorCount = MIN(ContributorCount)
FROM #Users
) ec ON ec.ContributorCount = u.ContributorCount
WHERE UserName NOT IN (#Editor, #Contributor1, #Contributor2, #Contributor3)
ORDER BY NEWID()
UPDATE #Users SET ContributorCount = ContributorCount + 1 WHERE UserName = #Contributor4
UPDATE #Solutions SET Contributor4 = #Contributor4 WHERE Contributor4 IS NULL
END
SELECT * FROM #Solutions
SELECT * FROM #Users
Here is some pseudo C# code.
Assuming you have two tables
1) User table which contains all the users
2) DailyTeam table which contains the users selected daily (your output)
struct Team
{
string name;
int editorCount;
}
currentEditorList is a List of Team
existingUserList is a List of Team
currentEditorList = Get Current Editor List from DailyTeam
existingUserList = Get All Users from User and its editor count (may need left outer join)
todayTeam is a new Array
// populate the normal users to dailyTeam
while (todayTeam count is less than 4)
{
randomIndex = generate a random number (from 0 to 9)
userName = get name from existingUserNames[randomIndex]
if (userName is not in todayTeam)
{
add userName to todayTeam
}
}
sort existingUserList by its editorCount
editorName = get the first item from existingUserList
add editorName to todayTeam
Note: I would implement this algorithm in powershell.
Here let me explain my solution or I should say logic, because I'm at a place where I DON'T have access to SQL Server.
So I'm not able to test it, you may have to edit to make it work. So explaining what my logic is..
First of all assuming that you will append a column (WHICH IS MUST for this logic)in your existing table say "unirow" which will have a unique number
assigned to each employee starting from 1.
Then yoy have to create a table tbl_counter with one column as number.There will be only one row (restriction)
and initially let it be 1.
As prerequisit is complete, now let's move to logic. All I did is made a self cross join for the Employees table five times
so that you have a unique combination of team. Now all need to done is to pick unique Editors each time this query/procedure
is executed. The output of this query/procedure will contain 5 columns 1st for editor and rest for Contributors.
BEGIN
DECLARE #counter number
DECLARE #limit number
DECLARE #Editor varchar(100)
select #limit=count(*) from Employees
select #counter=counter+1 from tbl_counter
IF(#counter>#limit)
begin
set #counter=1
update tbl_counter set counter=1
end
select #Editor=Name from Employees2 where id=#counter
select top 1 newid() as unirow,t1.name Editor,t2.name Contributor1,
t3.name Contributor2,t4.name Contributor3,t5.name Contributor4
from Employees t1,Employees t2,Employees t3,Employees t4,Employees t5
where t1.name<>t2.name and t1.name<>t3.name and t1.name<>t4.name and t1.name<>t5.name
and t2.name<>t1.name and t2.name<>t3.name and t2.name<>t4.name and t2.name<>t5.name
and t3.name<>t2.name and t3.name<>t1.name and t3.name<>t4.name and t3.name<>t5.name
and t4.name<>t2.name and t4.name<>t3.name and t4.name<>t1.name and t4.name<>t5.name
and t5.name<>t2.name and t5.name<>t3.name and t5.name<>t4.name and t5.name<>t1.name
and t1.name=#Editor
order by unirow
END

Join with dynamic pivot (version 2)

I have some tables with data:
Category
CategoryID CategoryName
1 Home
2 Contact
3 About
Position
PositionID PositionName
1 Main menu
2 Left menu
3 Right menu
...(new row can be added later)
CategoryPosition
CPID CID PID COrder
1 1 1 1
2 1 2 2
3 1 3 3
4 2 1 4
5 2 3 5
How can I make a table like this:
CID CName MainMenu LeftMenu RightMenu
1 Home 1 2 3
2 Contact 4 0 5
3 About 0 0 0
And if a new Category or Position row is added later, the query should reflect the change automatically, e.g:
CID CName MainMenu LeftMenu RightMenu BottomMenu
1 Home 1 2 3 0
2 Contact 4 0 5 0
3 About 0 0 0 0
4 News 0 0 0 0
The following dynamic query seems to work:
declare #columnlist nvarchar(4000)
select #columnlist = IsNull(#columnlist + ', ', '') + '[' + PositionName + ']'
from #Position
declare #query nvarchar(4000)
select #query = '
select *
from (
select CategoryId, CategoryName, PositionName,
IsNull(COrder,0) as COrder
from #Position p
cross join #Category c
left join #CategoryPosition cp
on cp.pid = p.PositionId
and cp.cid = c.CategoryId
) pv
PIVOT (max(COrder) FOR PositionName in (' + #columnlist + ')) as Y
ORDER BY CategoryId, CategoryName
'
exec sp_executesql #query
Some clarification:
The #columnlist contains the dymamic field list, built from the Positions table
The cross join creates a list of all categories and all positions
The left join seeks the corresponding COrder
max() selects the highest COrder per category+position, if there is more than one
PIVOT() turns the various PositionNames into separate columns
P.S. My table names begin with #, because I created them as temporary tables. Remove the # to refer to a permanent table.
P.S.2. If anyone wants to try his hands at this, here is a script to create the tables in this question:
set nocount on
if object_id('tempdb..#Category') is not null drop table #Category
create table #Category (
CategoryId int identity,
CategoryName varchar(50)
)
insert into #Category (CategoryName) values ('Home')
insert into #Category (CategoryName) values ('Contact')
insert into #Category (CategoryName) values ('About')
--insert into #Category (CategoryName) values ('News')
if object_id('tempdb..#Position') is not null drop table #Position
create table #Position (
PositionID int identity,
PositionName varchar(50)
)
insert into #Position (PositionName) values ('Main menu')
insert into #Position (PositionName) values ('Left menu')
insert into #Position (PositionName) values ('Right menu')
--insert into #Position (PositionName) values ('Bottom menu')
if object_id('tempdb..#CategoryPosition') is not null
drop table #CategoryPosition
create table #CategoryPosition (
CPID int identity,
CID int,
PID int,
COrder int
)
insert into #CategoryPosition (CID, PID, COrder) values (1,1,1)
insert into #CategoryPosition (CID, PID, COrder) values (1,2,2)
insert into #CategoryPosition (CID, PID, COrder) values (1,3,3)
insert into #CategoryPosition (CID, PID, COrder) values (2,1,4)
insert into #CategoryPosition (CID, PID, COrder) values (2,3,5)
Since PIVOT requires a static list of columns, I think a dynamic-sql-based approach is really all that you can do: http://www.simple-talk.com/community/blogs/andras/archive/2007/09/14/37265.aspx
As mentioned by several posters, dynamic SQL using the PIVOT command is the way to go. I wrote a stored proc named pivot_query.sql awhile back that has been very handy for this purpose. It works like this:
-- Define a query of the raw data and put it in a variable (no pre-grouping required)
declare #myQuery varchar(MAX);
set #myQuery = '
select
cp.cid,
c.CategoryName,
p.PositionName,
cp.COrder
from
CategoryPosition cp
JOIN Category c
on (c.CategoryId = cp.cid)
JOIN Position p
on (p.PositionId = cp.pid)';
-- Call the proc, passing the query, row fields, pivot column and summary function
exec dbo.pivot_query #myQuery, 'CategoryName', 'PositionName', 'max(COrder) COrder'
The full syntax of the pivot_query call is:
pivot_query '<query>', '<field list for each row>', '<pivot column>', '<aggregate expression list>', '[<results table>]', '[<show query>]'
it is explained more in the comments at the top of the source code.
A couple of advantages of this proc are that you can specify multiple summary functions like max(COrder),min(COrder) etc. and it has the option to store the output in a table in case you want to join the summary data up with other information.
I guess you need to select using PIVOT. By default, pivots only select a static list of columns. There are some solutions on the net dealing with dynamic column pivots, such as here and here.
My suggestion would be to return your data as a simple join and let the front end sort it out. There are some things for which SQL is excellent, but this particular problem seems like something that the front end should be doing. Of course, I can't know that without knowing your full situation, but that's my hunch.