Want to pivot table. And generate dynamic SQL string and execute it - sql

Create table script
CREATE TABLE #TableA
(
A VARCHAR(50),
Allocations INT,
Seats INT,
EndDate DATETIME
);
Insert table script
INSERT INTO #TableA
VALUES ('ABC',450,23,'2017-10-05'),
('ABC',23,765,'2017-05-01'),
('PQR',54,34,'2017-07-04'),
('ABC',234,45,'2017-11-27'),
('PQR',987,76,'2017-03-05'),
('ABC',76,65,'2017-02-23'),
('PQR',89,324,'2017-08-14'),
('ABC',45,34,'2017-07-13');
Output of #TableA
A | Allocations | Seats | EndDate |
-----+---------------+---------+-----------------------------+
ABC | 450 | 23 | 2017-10-05 00:00:00.000 |
ABC | 23 | 765 | 2017-05-01 00:00:00.000 |
PQR | 54 | 34 | 2017-07-04 00:00:00.000 |
ABC | 234 | 45 | 2017-11-27 00:00:00.000 |
PQR | 987 | 76 | 2017-03-05 00:00:00.000 |
ABC | 76 | 65 | 2017-02-23 00:00:00.000 |
PQR | 89 | 324 | 2017-08-14 00:00:00.000 |
ABC | 45 | 34 | 2017-07-13 00:00:00.000 |
I want the following query to be dynamic:
WITH T AS
(
SELECT
A, thing, priority, value, d
FROM
#TableA
CROSS APPLY (VALUES(CAST(EndDate AS DATE)))D(d)
CROSS APPLY (VALUES(1, 'A', NULL),
(2, 'Allocations', Allocations),
(3, 'Seats', Seats)) V(priority, thing, value)
)
SELECT
thing,
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-02-23],0) AS VARCHAR(50)) END AS [2017-02-23],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-03-05],0) AS VARCHAR(50)) END AS [2017-03-05],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-05-01],0) AS VARCHAR(50)) END AS [2017-05-01],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-07-04],0) AS VARCHAR(50)) END AS [2017-07-04],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-07-13],0) AS VARCHAR(50)) END AS [2017-07-13],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-08-14],0) AS VARCHAR(50)) END AS [2017-08-14],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-10-05],0) AS VARCHAR(50)) END AS [2017-10-05],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-11-27],0) AS VARCHAR(50)) END AS [2017-11-27]
FROM
T
PIVOT
(SUM(value) FOR d in (
[2017-02-23],
[2017-03-05],
[2017-05-01],
[2017-07-04],
[2017-07-13],
[2017-08-14],
[2017-10-05],
[2017-11-27])) P
ORDER BY A, priority;
Here I want dynamic string to generate EndDate column and execute that query and will have to give output.

You can use this script.
DECLARE #ColumnNamesSelect NVARCHAR(MAX) =''
SELECT #ColumnNamesSelect = #ColumnNamesSelect + ', case when thing = ''A'' THEN A ELSE CAST(ISNULL(' + QUOTENAME ( ColName ) + ',0) AS VARCHAR(50)) END AS ' + QUOTENAME ( ColName )
FROM (SELECT DISTINCT CONVERT(VARCHAR(10),EndDate,120) ColName FROM #TableA ) AS T
SET #ColumnNamesSelect = STUFF(#ColumnNamesSelect,1,1,'')
DECLARE #ColumnNamesWhere NVARCHAR(MAX) =''
SELECT #ColumnNamesWhere = #ColumnNamesWhere + ', ' + QUOTENAME ( ColName )
FROM (SELECT DISTINCT CONVERT(VARCHAR(10),EndDate,120) ColName FROM #TableA ) AS T
SET #ColumnNamesWhere = STUFF(#ColumnNamesWhere,1,1,'')
DECLARE #SqlText NVARCHAR(MAX) =
'WITH T
AS (SELECT A,
thing,
priority,
value,
d
FROM #TableA
CROSS APPLY (VALUES(CAST(EndDate AS DATE)))D(d)
CROSS APPLY (VALUES(1, ''A'', NULL),
(2, ''Allocations'', Allocations),
(3, ''Seats'', Seats)) V(priority, thing, value))
SELECT thing, ' + #ColumnNamesSelect +
' FROM T
PIVOT (SUM(value) FOR d IN (' + #ColumnNamesWhere + ') ) P'
+' ORDER BY A, priority;'
EXEC sp_executesql #SqlText
print #SqlText
DROP TABLE #TableA
Result:
thing 2017-02-23 2017-03-05 2017-05-01 2017-07-04 2017-07-13 2017-08-14 2017-10-05 2017-11-27
----------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------------------------------------------------
A ABC ABC ABC ABC ABC ABC ABC ABC
Allocations 76 0 23 0 45 0 450 234
Seats 65 0 765 0 34 0 23 45
A PQR PQR PQR PQR PQR PQR PQR PQR
Allocations 0 987 0 54 0 89 0 0
Seats 0 76 0 34 0 324 0 0

Related

How to convert SQL Server columns into rows?

I need help to switch columns with rows in SQL. Need to turn this:
+------------+------------+-------------+------+
| Date | Production | Consumption | .... |
+------------+------------+-------------+------+
| 2017-01-01 | 100 | 1925 | |
| 2017-01-02 | 200 | 2005 | |
| 2017-01-03 | 150 | 1998 | |
| 2017-01-04 | 250 | 2200 | |
| 2017-01-05 | 30 | 130 | |
|... | | | |
+------------+------------+-------------+------+
into this:
+------------+------------+------------+------------+------------+-----+
| 01-01-2017 | 02-01-2017 | 03-01-2017 | 04-01-2017 | 05-01-2017 | ... |
+------------+------------+------------+------------+------------+-----+
| 100 | 200 | 150 | 250 | 30 | |
| 1925 | 2005 | 1998 | 2200 | 130 | |
+------------+------------+------------+------------+------------+-----+
Can someone help me? Should I use PIVOT?
EDIT: I've tried using some suggestions like PIVOT and UNPIVOT, but I could not achieve the expected result.
I've tried:
SELECT *
FROM (
SELECT date, Consumption
FROM Energy
where date < '2017-02-01'
) r
pivot (sum(Consumption) for date in ([2017-01-01],[2017-01-02],[2017-01-03]....)) c
order by 1
However with the above query I only managed to get some of what I need,
+------------+------------+------------+------------+------------+-----+
| 01-01-2017 | 02-01-2017 | 03-01-2017 | 04-01-2017 | 05-01-2017 | ... |
+------------+------------+------------+------------+------------+-----+
| 100 | 200 | 150 | 250 | 30 | |
+------------+------------+------------+------------+------------+-----+
I need to have production and consumption, all in the same query, but I can only get one of them.
Is it possible to put more than one column in PIVOT? I've tried, but unsuccessfully.
You can achieve the desired output with dynamic sql, but be aware of performance and security problems (i.e. SQL injection) of this approach.
--create test table
CREATE TABLE dbo.Test (
[Date] date
, Production int
, Consumption int
)
--populate test table with values
insert into dbo.Test
values
('2017-01-01', 100, 1925)
,('2017-01-02', 200, 2005)
,('2017-01-03', 150, 1998)
,('2017-01-04', 250, 2200)
,('2017-01-05', 30, 130)
--table variable that will hold the names of all columns to pivot
declare #columNames table (ColumnId int identity (1,1), ColumnName varchar(255))
--variable that will hold the total number of columns to pivot
declare #columnCount int
--variable that will be used to run through the columns to pivot
declare #counter int = 1
--this variable holds all column names
declare #headers nvarchar(max) = ''
--this variable contains the TSQL dinamically generated
declare #sql nvarchar(max) = ''
--populate list of columns to pivot
insert into #columNames
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where
TABLE_NAME = 'test'
and TABLE_SCHEMA = 'dbo'
and COLUMN_NAME <>'date'
--populate column total counter
select #columnCount = count(*) from #columNames
--populate list of headers of the result table
select #headers = #headers + ', ' + quotename([Date])
from dbo.Test
set #headers = right(#headers, len(#headers) - 2)
--run through the table containing the columns names and generate the dynamic sql query
while #counter <= #columnCount
begin
select #sql = #sql + ' select piv.* from (select [Date], '
+ quotename(ColumnName) + ' from dbo.Test) p pivot (max('
+ quotename(ColumnName) + ') for [Date] in ('
+ #headers + ') ) piv '
from #columNames where ColumnId = #counter
--add union all except when we are concatenating the last pivot statement
if #counter < #columnCount
set #sql = #sql + ' union all'
--increment counter
set #counter = #counter + 1
end
--execute the dynamic query
exec (#sql)
Result:
Now if you add a column and some more rows:
--create test table
CREATE TABLE [dbo].[Test] (
[Date] date
, Production int
, Consumption int
, NewColumn int
)
--populate test table with values
insert into [dbo].[Test]
values
('2017-01-01', 100, 1925 , 10)
,('2017-01-02', 200, 2005, 20)
,('2017-01-03', 150, 1998, 30)
,('2017-01-04', 250, 2200, 40)
,('2017-01-05', 30, 130 , 50)
,('2017-01-06', 30, 130 , 60)
,('2017-01-07', 30, 130 , 70)
this is the result:

How to get SQL query result column name as first row

I have a dynamic SQL query that gets me result sets after execution. However, the UI model that I am rendering results back from SQL server engine doesn't provide a way to render query column names.
Due to the dynamic nature of the query, I can't hard code the column names at design time. So my question is how do I get column names along with the data set returned by the query?
This Query:
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
convert(date, DATEADDED) DATEADDED
,COUNT(1) as NUMBEROFRECORDS
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
Gives me this table (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
But I want this (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
Thanks
It's doable, but not very pretty. A Stored Procedure where you pass the dynamic SQL would be much cleaner
We're essentially doing Dynamic SQL within Dynamic SQL
One caveat: I reserved the field RN
Example (Using my FRED Series Data)
-- This is Your Base/Initial Query, or the only portion you need to supply
Declare #SQL varchar(max) = 'Select Updated as Updated,Count(*) as NumberOfRecords From [dbo].[FRED-Series] Group By Updated'
Select #SQL = '
;with cte0 as ('+#SQL+')
, cte1 as (Select *,RN = Row_Number() over (Order By (Select null)) From cte0 )
, cte2 as (
Select A.RN,C.*
From cte1 A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value(''local-name(.)'',''varchar(100)'')
,Value = attr.value(''.'',''varchar(max)'')
,ColNr = Row_Number() over (Order By (Select Null))
From B.XMLData.nodes(''/row'') as A(r)
Cross Apply A.r.nodes(''./#*'') AS B(attr)
Where attr.value(''local-name(.)'',''varchar(100)'') not in (''RN'')
) C
)
Select Distinct RN=0,Item,Value=Item,ColNr Into #Temp From cte2 Union All Select * from cte2
Declare #SQL varchar(max) = Stuff((Select '','' + QuoteName(Item) From #Temp Where RN=0 Order by ColNr For XML Path('''')),1,1,'''')
Select #SQL = ''Select '' + #SQL + '' From (Select RN,Item,Value From #Temp ) A Pivot (max(Value) For [Item] in ('' + #SQL + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
Updated NumberOfRecords
Updated NumberOfRecords
2017-03-22 597
2017-03-23 40
2017-03-20 228
2017-03-21 1404
Just some Commentary
cte0 is your primary query
cte1 will take the results of your initial query and add a Row Number
cte2 will dynamically unpivot your data
The results of cte2 are dropped into a #temp table for convenience (assuming this is allowed)
Then we perform a dynamic pivot
Union a static query with the column names. You have to cast the results of the second query to varchar or nvarchar so they are the same data type as your column names.
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
''DATEADDED'' AS [DATEADDED]
,''NUMBEROFRECORDS'' AS [NUMBEROFRECORDS]
SELECT
CAST(convert(date, DATEADDED) AS NVARCHAR(MAX)
,CAST(COUNT(1) AS NVARCHAR(MAX))
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
With this said, you should be able to reference the column names via code and not have to add them to the query. This way you could keep the data types of the result set.

SQL SELECT: concatenated column with line breaks and heading per group

I have the following SQL result from a SELECT query:
ID | category| value | desc
1 | A | 10 | text1
2 | A | 11 | text11
3 | B | 20 | text20
4 | B | 21 | text21
5 | C | 30 | text30
This result is stored in a temporary table named #temptab. This temporary table is then used in another SELECT to build up a new colum via string concatenation (don't ask me about the detailed rationale behind this. This is code I took from a colleague). Via FOR XML PATH() the output of this column is a list of the results and is then used to send mails to customers.
The second SELECT looks as follows:
SELECT t1.column,
t2.column,
(SELECT t.category + ' | ' + t.value + ' | ' + t.desc + CHAR(9) + CHAR(13) + CHAR(10)
FROM #temptab t
WHERE t.ID = ttab.ID
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)') AS colname
FROM table1 t1
...
INNER JOIN #temptab ttab on ttab.ID = someOtherTable.ID
...
Without wanting to go into too much detail, the column colname becomes populated with several entries (due to multiple matches) and hence, a longer string is stored in this column (CHAR(9) + CHAR(13) + CHAR(10) is essentially a line break). The result/content of colname looks like this (it is used to send mails to customers):
A | 10 | text1
A | 11 | text11
B | 20 | text20
B | 21 | text21
C | 30 | text30
Now I would like to know, if there is a way to more nicely format this output string. The best case would be to group the same categories together and add a heading and empty line between different categories:
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
My question is: How do I have to modify the above query (especially the string-concatenation-part) to achieve above formatting? I was thinking about using a GROUP BY statement, but this obviously does not yield the desired result.
Edit: I use Microsoft SQL Server 2008 R2 (SP2) - 10.50.4270.0 (X64)
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30')
Declare #String varchar(max) = ''
Select #String = #String + Case when RowNr=1 Then Replicate(char(13)+char(10),2) +'*'+Category+'*' Else '' end
+ char(13)+char(10) + category + ' | ' + cast(value as varchar(25)) + ' | ' + [desc]
From (
Select *
,RowNr=Row_Number() over (Partition By Category Order By Value)
From #YourTable
) A Order By Category, Value
Select Substring(#String,5,Len(#String))
Returns
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
This should return what you want
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30');
WITH Categories AS
(
SELECT category
,'**' + category + '**' AS CatCaption
,ROW_NUMBER() OVER(ORDER BY category) AS CatRank
FROM #YourTable
GROUP BY category
)
,Grouped AS
(
SELECT c.CatRank
,0 AS ValRank
,c.CatCaption AS category
,-1 AS ID
,'' AS Value
,'' AS [desc]
FROM Categories AS c
UNION ALL
SELECT c.CatRank
,ROW_NUMBER() OVER(PARTITION BY t.category ORDER BY t.Value)
,t.category
,t.ID
,CAST(t.value AS VARCHAR(100))
,t.[desc]
FROM #YourTable AS t
INNER JOIN Categories AS c ON t.category=c.category
)
SELECT category,Value,[desc]
FROM Grouped
ORDER BY CatRank,ValRank
The result
category Value desc
**A**
A 10 text1
A 11 text11
**B**
B 20 text20
B 21 text21
**C**
C 30 text30

Can we use two pivot in a single query?

Declare #tbl table(SessionId varchar(max),ItemID_FK int,Roles varchar(max))
insert into #tbl
select distinct SessionID,ItemID_FK,Roles from tbl_Answers where ID_FK=#ID
SELECT ItemID_PK,ItemName,case when [Role1] IS NULL then 0 else [Role1] end as [Role1],
case when [Role2] IS NULL then 0 else [Role2] end as [Role2],
case when [Role3] IS NULL then 0 else [Role3] end as [Role3],
case when [Role4] IS NULL then 0 else [Role4] end as [Role4],
case when [Role5] IS NULL then 0 else [Role5] end as [Role5],
case when [Role6] IS NULL then 0 else [Role6] end as [Role6],
case when [Role7] IS NULL then 0 else [Role7] end as [Role7]
FROM
(
select items.ItemID_PK ,items.ItemName,count(ans.Roles) as cntRoles,ans.Roles from tbl_Items items Full join #tbl ans
on items.ItemID_PK=ans.ItemID_FK where items.ID_FK= #ID group by Roles,ItemName , items.ItemID_PK
) d PIVOT
(
max(cntRoles)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt order by ItemID_PK
I used the above stored procedure and got the output as
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
|ItemID_PK |ItemName |Role1|Role2|Role3|Role4|Role5|Role6|Role7|
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
| 111 | aaaaa | 6 | 5 | 0 | 5 | 1 | 4 | 2 |
| 222 | bbbbb | 1 | 1 | 7 | 2 | 0 | 3 | 1 |
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
I have another query and got the following output.
Select Category,Answer,Roles
from tbl_Answers where ID_FK=1 and Category='OtherText'
+---------+--------+-----+
|Category |Answer |Roles|
+---------+--------+-----+
|OtherText| xxx |Role1|
|OtherText| yyy |Role1|
|OtherText| zzz |Role2|
|OtherText| xzx |Role3|
+---------+--------+-----+
I need to merge the above two outputs to generate the result as
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
|ItemID_PK |ItemName |Role1|Role2|Role3|Role4|Role5|Role6|Role7|
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
| 111 | aaaaa | 6 | 5 | 0 | 5 | 1 | 4 | 2 |
| 222 | bbbbb | 1 | 1 | 7 | 2 | 0 | 3 | 1 |
| Null | Othertext| xxx | zzz | xzx | saa | | xxx | |
| Null | Othertext| yyy | | | zxz | | | |
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
How to combine the second query to the first pivot query to get the result mentioned above?
Thanks in advance.
You could just use UNION ALL to combine the two results, you would need to convert the roles from the top query from int to VARCHAR though:
DECLARE #ID INT = 1;
WITH Ans AS
( SELECT DISTINCT SessionID,ItemID_FK,Roles
FROM tbl_Answers
WHERE ID_FK = #ID
), PivotData AS
( SELECT items.ItemID_PK,
items.ItemName,
cntRoles = COUNT(ans.Roles),
ans.Roles
FROM tbl_Items items
FULL JOIN Ans
ON items.ItemID_PK = ans.ItemID_FK
WHERE items.ID_FK = #ID
GROUP BY Roles,ItemName, items.ItemID_PK
)
SELECT ItemID_PK,
ItemName,
[Role1] = CAST(ISNULL([Role1], 0) AS VARCHAR(255)),
[Role2] = CAST(ISNULL([Role2], 0) AS VARCHAR(255)),
[Role3] = CAST(ISNULL([Role3], 0) AS VARCHAR(255)),
[Role4] = CAST(ISNULL([Role4], 0) AS VARCHAR(255)),
[Role5] = CAST(ISNULL([Role5], 0) AS VARCHAR(255)),
[Role6] = CAST(ISNULL([Role6], 0) AS VARCHAR(255)),
[Role7] = CAST(ISNULL([Role7], 0) AS VARCHAR(255))
FROM PivotData
PIVOT
( MAX(cntRoles)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt
UNION ALL
SELECT ItemID_PK = NULL,
ItemName = Category,
[Role1] = ISNULL([Role1], ''),
[Role2] = ISNULL([Role2], ''),
[Role3] = ISNULL([Role3], ''),
[Role4] = ISNULL([Role4], ''),
[Role5] = ISNULL([Role5], ''),
[Role6] = ISNULL([Role6], ''),
[Role7] = ISNULL([Role7], '')
FROM ( SELECT Category,Answer,Roles
FROM tbl_Answers
WHERE ID_FK = 1
AND Category = 'OtherText'
) pd
PIVOT
( MAX(Answer)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt
ORDER BY ItemID_PK;
Note, I have changed this expression:
case when [Role2] IS NULL then 0 else [Role2] end
to
ISNULL([Role2], 0)
as the effect is the same, but it is much shorter. I have also removed the table variable, and just placed the same query within a Common Table Expression, as it seems redundant to fill a table variable then only refer to it once. You are removing the use of indexes and statistics on the actual table and gaining no benefit for it.
Use a UNION to combine the two pivots.

Combining like data from columns into single row

I'm trying to combine partial contents of rows that are the result set of a query from SQL Server 2005 that reads a .CSV. Here's a simplified version of the data I have:
objectID | value1 | value2
_________________________________
12 | R | 100
12 | R | 101
12 | S | 220
13 | D | 88
14 | K | 151
14 | K | 152
What I'm trying to get to is a grouping of each objectID's values on the same row, so that there is one and only one row for each objectID. In graphical terms:
objectID | value1a | value2a | value 1b | value2b | value1c | value2c
______________________________________________________________________________
12 | R | 100 | R | 101 | S | 220
13 | D | 88 | | | |
14 | K | 151 | K | 152 | |
Blank cells are blank.
I've been hoping to do this in Excel or Access without VB, but CONCAT and other similar functions (and responses here and elsewhere suggesting similar approaches) don't work because each value needs to stay in its own cell (this data will eventually be merged with a Word form). If the answer's a SQL stored procedure or cursor, that's okay, though I'm not terribly efficient at writing them just yet.
Thanks to all.
First import the data into a temp table. The temp table will end up something like this sample data:
create table #tmp (objectID int, value1 char(1), value2 int)
insert #tmp select
12 ,'R', 100 union all select
12 ,'R', 101 union all select
12 ,'S', 220 union all select
13 ,'D', 88 union all select
14 ,'K', 151 union all select
14 ,'K', 152
Then, you can use this SQL batch - which can be put into a Stored Procedure if required.
declare #sql nvarchar(max)
select #sql = ISNULL(#sql+',','')
+ 'max(case when rn=' + cast(number as varchar) + ' then value1 end) value' + cast(number as varchar) + 'a,'
+ 'max(case when rn=' + cast(number as varchar) + ' then value2 end) value' + cast(number as varchar) + 'b'
from master..spt_values
where type='P' and number between 1 and (
select top 1 COUNT(*)
from #tmp
group by objectID
order by 1 desc)
set #sql = '
select objectID, ' + #sql + '
from (
select rn=ROW_NUMBER() over (partition by objectID order by value2), *
from #tmp) p
group by ObjectID'
exec (#sql)
Output
objectID value1a value1b value2a value2b value3a value3b
----------- ------- ----------- ------- ----------- ------- -----------
12 R 100 R 101 S 220
13 D 88 NULL NULL NULL NULL
14 K 151 K 152 NULL NULL
Warning: Null value is eliminated by an aggregate or other SET operation.