Merge multiple rows into a single row - sql-server

I would like to know the best approach to merge data from the following rows into a single row in another view.
These are the results as they are currently displayed;
Type_ID | Client_ID | PBX_Vendor |
127 | 090820006311404926326C | Aastra |
127 | 090820006311404926326C | Ericsson |
127 | 111012237401404926326C | Aastra |
127 | 120209287521404926326C | Aastra |
127 | 120209287521404926326C | Alcatel |
The following is how I would like to see the data;
Type_ID | Client_ID | PBX_Vendor |
127 | 090820006311404926326C | Aastra, Ericsson |
127 | 111012237401404926326C | Aastra |
127 | 120209287521404926326C | Aastra, Alcatel |
Basically, there are multiple PBX Vendors associated with a Client ID. I need this display in a single row for a helpdesk system.
I have attempted this already with CONCAT, but all I end up with is a single row with over 100 vendors in it that are not specific to a Client_ID.
Any help with be very much appreciated!

Here's A way to do it (also works with 2005):
Table
DECLARE #table TABLE
(
[Type_ID] INT,
[Client_ID] VARCHAR(50),
[PBX_Vendor] VARCHAR(50)
)
Data
INSERT INTO #table
SELECT 127,
'090820006311404926326C',
'Aastra'
UNION ALL
SELECT 127,
'090820006311404926326C',
'Ericsson'
UNION ALL
SELECT 127,
'111012237401404926326C',
'Aastra'
UNION ALL
SELECT 127,
'120209287521404926326C',
'Aastra'
UNION ALL
SELECT 127,
'120209287521404926326C',
'Alcatel'
Query
SELECT [Type_ID],
[Client_ID],
(
SELECT STUFF((
SELECT ',' + [PBX_Vendor]
FROM #table
WHERE [Client_ID] = tbl.[Client_ID]
AND [Type_ID] = tbl.[Type_ID]
GROUP BY [PBX_Vendor]
ORDER BY [PBX_Vendor]
FOR
XML PATH('')
), 1, 1, '')
) PBX_Vendor
FROM #table tbl
GROUP BY [Type_ID],
[Client_ID]
Result
Type_ID Client_ID PBX_Vendor
127 090820006311404926326C Aastra,Ericsson
127 111012237401404926326C Aastra
127 120209287521404926326C Aastra,Alcatel

Dan, I have managed to get this working using your original Ideas with some modifications. Although I cannot save it as a view as I understand that you cannot save DECLARES as VIEWS;
DECLARE #table TABLE
(
[Type_ID] INT,
[Client_ID] VARCHAR(50),
[PBX_Vendor] VARCHAR(50)
)
INSERT INTO #table
SELECT dbo.AMGR_User_Fields_Tbl.Type_Id, dbo.AMGR_User_Fields_Tbl.Client_Id, dbo.AMGR_User_Field_Defs_Tbl.Description AS PBX_Vendor
FROM dbo.AMGR_User_Fields_Tbl INNER JOIN
dbo.AMGR_User_Field_Defs_Tbl ON dbo.AMGR_User_Fields_Tbl.Type_Id = dbo.AMGR_User_Field_Defs_Tbl.Type_Id AND
dbo.AMGR_User_Fields_Tbl.Code_Id = dbo.AMGR_User_Field_Defs_Tbl.Code_Id
WHERE (dbo.AMGR_User_Fields_Tbl.Type_Id = 127)
SELECT [Type_ID],
[Client_ID],
(
SELECT STUFF((
SELECT ', ' + [PBX_Vendor]
FROM #table
WHERE [Client_ID] = tbl.[Client_ID]
AND [Type_ID] = tbl.[Type_ID]
GROUP BY [PBX_Vendor]
ORDER BY [PBX_Vendor]
FOR
XML PATH('')
), 1, 1, '')
) PBX_Vendor
FROM #table tbl
GROUP BY [Type_ID],
[Client_ID]

Related

How to check what column in INSERT do not have the correct data type?

Imagine I have 200 columns in one INSERT statement, and I occasionally get an "Cannot convert" error for one of columns. Things is, I do not know which column causes this error.
Is there any way in T-SQL or mybatis to check WHICH column has the incorrect format? (I have just date, char, numeric). I can use ISNUMERIC, ISDATE for every column, but this is not so elegant.
I'm using mybatis in Java, so I cannot use any PreparedStatement or so.
You could build a query that tries to convert each of the suspected columns.
And limit the query to where one of the attempts to convert fails.
Mostly the bad data will be in CHAR's or VARCHAR's when trying to cast or convert them to a datetime or number type.
So you can limit your research to those.
Also, from the error you should see which value failed to convert to which type. Which can also help to limit which fields you research.
A simplified example using table variables:
declare #T1 table (id int identity(1,1) primary key, field1 varchar(30), field2 varchar(30), field3 varchar(30));
declare #T2 table (id int identity(1,1) primary key, field1_int int, field2_date date, field3_dec decimal(10,2));
insert into #T1 (field1, field2, field3) values
('1','2018-01-01','1.23'),
('not an int','2018-01-01','1.23'),
('1','not a date','1.23'),
('1','2018-01-01','not a decimal'),
(null,'2018-01-01','1.23'),
('1',null,'1.23'),
('1','2018-01-01',null)
;
select top 1000
id,
case when try_convert(int, field1) is null then field1 end as field1,
case when try_convert(date, field2) is null then field2 end as field2,
case when try_convert(decimal(10,4), field3) is null then field3 end as field3
from #T1
where
try_convert(int, coalesce(field1, '0')) is null
or try_convert(date, coalesce(field2, '1900-01-01')) is null
or try_convert(decimal(10,4), coalesce(field3, '0.0')) is null;
Returns:
id field1 field2 field3
-- ---------- ----------- -------------
2 not an int NULL NULL
3 NULL not a date NULL
4 NULL NULL not a decimal
If the origin data doesn't have to much bad data you could try to fix the origin data first.
Or use the try_convert for the problematic columns with bad data.
For example:
insert into #T2 (field1_int, field2_date, field3_dec)
select
try_convert(int, field1),
try_convert(date, field2),
try_convert(decimal(10,4), field3)
from #T1;
With larger imports - especially when you expect issues - a two-stepped approach is highly recommended.
import the data to a very tolerant staging table (all NVARCHAR(MAX))
check, evaluate, manipulate, correct whatever is needed and do the real insert from here
Here is a generic approach you might adapt to your needs. It will check all tables values against a type-map-table and output all values, which fail in TRY_CAST (needs SQL-Server 2012+)
A table to mockup the staging table (partly borrowed from LukStorms' answer - thx!)
CREATE TABLE #T1 (id INT IDENTITY(1,1) PRIMARY KEY
,fldInt VARCHAR(30)
,fldDate VARCHAR(30)
,fldDecimal VARCHAR(30));
GO
INSERT INTO #T1 (fldInt, fldDate, fldDecimal) values
('1','2018-01-01','1.23'),
('blah','2018-01-01','1.23'),
('1','blah','1.23'),
('1','2018-01-01','blah'),
(null,'2018-01-01','1.23'),
('1',null,'1.23'),
('1','2018-01-01',null);
--a type map (might be taken from INFORMATION_SCHEMA of an existing target table automatically)
DECLARE #type_map TABLE(ColumnName VARCHAR(100),ColumnType VARCHAR(100));
INSERT INTO #type_map VALUES('fldInt','int')
,('fldDate','date')
,('fldDecimal','decimal(10,2)');
--The staging table's name
DECLARE #TableName NVARCHAR(100)='#T1';
--dynamically created statements for each column
DECLARE #columnSelect NVARCHAR(MAX)=
(SELECT
' UNION ALL SELECT id ,''' + tm.ColumnName + ''',''' + tm.ColumnType + ''',' + QUOTENAME(tm.ColumnName)
+ ',CASE WHEN TRY_CAST(' + QUOTENAME(tm.ColumnName) + ' AS ' + tm.ColumnType + ') IS NULL THEN 0 ELSE 1 END ' +
'FROM ' + QUOTENAME(#TableName)
FROM #type_map AS tm
FOR XML PATH('')
);
-The final dynamically created statement
DECLARE #cmd NVARCHAR(MAX)=
'SELECT tbl.*
FROM
(
SELECT 0 AS id,'''' AS ColumnName,'''' AS ColumnType,'''' AS ColumnValue,0 AS IsValid WHERE 1=0 '
+ #columnSelect +
') AS tbl
WHERE tbl.IsValid = 0;'
--Execution with EXEC()
EXEC(#cmd);
The result:
+----+------------+---------------+-------------+---------+
| id | ColumnName | ColumnType | ColumnValue | IsValid |
+----+------------+---------------+-------------+---------+
| 2 | fldInt | int | blah | 0 |
+----+------------+---------------+-------------+---------+
| 5 | fldInt | int | NULL | 0 |
+----+------------+---------------+-------------+---------+
| 3 | fldDate | date | blah | 0 |
+----+------------+---------------+-------------+---------+
| 6 | fldDate | date | NULL | 0 |
+----+------------+---------------+-------------+---------+
| 4 | fldDecimal | decimal(10,2) | blah | 0 |
+----+------------+---------------+-------------+---------+
| 7 | fldDecimal | decimal(10,2) | NULL | 0 |
+----+------------+---------------+-------------+---------+
The statement created is like here:
SELECT tbl.*
FROM
(
SELECT 0 AS id,'' AS ColumnName,'' AS ColumnType,'' AS ColumnValue,0 AS IsValid WHERE 1=0
UNION ALL SELECT id
,'fldInt'
,'int'
,[fldInt]
,CASE WHEN TRY_CAST([fldInt] AS int) IS NULL THEN 0 ELSE 1 END
FROM [#T1]
UNION ALL SELECT id
,'fldDate'
,'date',[fldDate]
,CASE WHEN TRY_CAST([fldDate] AS date) IS NULL THEN 0 ELSE 1 END
FROM [#T1]
UNION ALL SELECT id
,'fldDecimal'
,'decimal(10,2)'
,[fldDecimal]
,CASE WHEN TRY_CAST([fldDecimal] AS decimal(10,2)) IS NULL THEN 0 ELSE 1 END
FROM [#T1]
) AS tbl
WHERE tbl.IsValid = 0;

SQL: create new record based on several ones

I have a question about sql query. Assume we have one table
Reports:
+----+-------------+---------+--------+
| ID | reporter_id | subject | report |
+----+-------------+---------+--------+
| 1 | 1 | A | "OK" |
| 2 | 1 | B | "OK" |
| 3 | 1 | c |"NOT OK"|
| 4 | 2 | A | "OK" |
| 5 | 2 | C | "OK" |
+----+-------------+---------+--------+
Now, what I want to do is to select all the reports for each reporter_id and produce the following table:
+-------------+-----------------+-----------------+-----------------+
| reporter_id | report_subjectA | report_subjectB | report_subjectC |
+-------------+-----------------+-----------------+-----------------+
| 1 | "OK" | "OK" | "NOT OK" |
| 2 | "OK" | NULL | "OK" |
+-------------+-----------------+-----------------+-----------------+
Is is possible to do that?
Thank you
If subjects are unlimited, use this:
SELECT * INTO #src FROM
(VALUES
(1, 1, 'A', 'OK' ),
(2, 1, 'B', 'OK' ),
(3, 1, 'C', 'NOT OK'),
(4, 2, 'A', 'OK' ),
(5, 2, 'C', 'OK' )
)T(ID, reporter_id, subject, report)
DECLARE #columns nvarchar(MAX) = STUFF((
SELECT DISTINCT ',[Report Subject '+subject+']'
FROM #Src
FOR XML PATH('')), 1, 1, '')
DECLARE #sql nvarchar(MAX) = N'
SELECT * FROM
(
SELECT reporter_id, ''Report Subject ''+subject Title, report
FROM #Src
) T
PIVOT
(MAX(report) FOR Title IN ('+#columns+')) P'
EXEC (#sql)
If there is only 3, use simplified PIVOT:
SELECT * FROM
(
SELECT reporter_id, 'Report Subject '+subject Title, report
FROM #Src
) T
PIVOT
(MAX(report) FOR Title IN ([Report Subject A], [Report Subject B], [Report Subject C])) P
Both return:
reporter_id Report Subject A Report Subject B Report Subject C
----------- ---------------- ---------------- ----------------
1 OK OK NOT OK
2 OK NULL OK
You can as the below:
SELECT
reporter_id,
MIN(CASE WHEN UPPER([subject]) = 'A' THEN report ELSE NULL END) report_subjectA,
MIN(CASE WHEN UPPER([subject]) = 'B' THEN report ELSE NULL END) report_subjectB,
MIN(CASE WHEN UPPER([subject]) = 'C' THEN report ELSE NULL END) report_subjectC
FROM YourTable
GROUP BY
reporter_id
Try this :
declare #id int,#a char(10),#b char(10),#c char(10)
declare #reports table (reporter_id int, report_subjectA char(10), report_subjectB char(10), report_subjectC char(10))
declare c cursor for
select distinct reporter_id from reports
open c
fetch next from c into #id
while ##FETCH_STATUS=0
begin
set #a=NULL
set #B=NULL
set #c=NULL
if exists(select report from reports where subjects='A' and reporter_id =#id) set #a=(select report from reports where subjects='A' and reporter_id =#id)
if exists (select report from reports where subjects='B' and reporter_id =#id) set #b=(select report from reports where subjects='B' and reporter_id =#id)
if exists (select report from reports where subjects='C' and reporter_id =#id) set #c=(select report from reports where subjects='C' and reporter_id =#id)
insert into #reports values (#id,#a,#b,#c)
fetch next from c into #id
end
close c
deallocate c
select * from #reports
If there are only 3 subjects.
Select reporter_id, report_subjectA, report_subjectB, report_subjectC from
(
Select reporter_id, report, rep_sub = 'report_subject' + [Subject] from TableReports r
) SourceT
Pivot
(
max(report) for rep_sub in ([report_subjectA],[report_subjectB],[report_subjectC])
) AS PivotedT

How to retrieve unique records having unique values in two columns from a table in SQL Server

I want to query a table where I need the result that contains unique values from two columns together. For e.g.
Table
EnquiryId | EquipmentId | Price
-----------+--------------+-------
1 | E20 | 10
1 | E50 | 40
1 | E60 | 20
2 | E30 | 90
2 | E20 | 10
2 | E90 | 10
3 | E90 | 10
3 | E60 | 10
For each EnquiryId, EquipmentId will be unique in the table. Now I want a result where I can get something like this
EnquiryId | EquipmentId | Price
-----------+--------------+-------
1 | E20 | 10
2 | E30 | 90
3 | E90 | 10
In the result each enquiryId present in the table should be displayed uniquely.
If suppose I have 3 EquipmentIds "E20,E50,E60" for EnquiryId "1".. Any random EquipmentId should be displayed from these three values only.
Any help would be appreciated. Thank you in advance.
QUERY
;WITH cte AS
(
SELECT *,
ROW_NUMBER() OVER
(PARTITION BY enquiryID
ORDER BY enquiryID ) AS RN
FROM tbl
)
SELECT enquiryID,equipmentID,Price
FROM cte
WHERE RN=1
FIND FIDDLE HERE
The following code must help you..
Sorry that I ended up in a lengthy solution only. Run it in your SSMS and see the result.
Declare #tab table (EnquiryId int, EquipmentId varchar(10),Price int)
Insert into #tab values
(1,'E20',10),
(1,'E50',40),
(1,'E60',20),
(2,'E30',90),
(2,'E20',10),
(2,'E90',10),
(3,'E90',10),
(3,'E60',10)
----------------------------------------------
Declare #s int = 1
Declare #e int,#z varchar(10)
Declare #Equipment table (EquipmentId varchar(10),ind int)
Insert into #Equipment (EquipmentId) Select Distinct EquipmentId From #tab
Declare #Enquiry table (id int identity(1,1),EnquiryId int,EquipmentId varchar(10))
Insert into #Enquiry (EnquiryId) Select Distinct EnquiryId From #tab
Set #e = ##ROWCOUNT
While #s <= #e
begin
Select Top 1 #z = T.EquipmentId
From #tab T
Join #Enquiry E On T.EnquiryId = E.EnquiryId
Join #Equipment Eq On Eq.EquipmentId = T.EquipmentId
Where E.id = #s
And Eq.ind is Null
Order by NEWID()
update #Enquiry
Set EquipmentId = #z
Where id = #s
update #Equipment
Set ind = 1
Where EquipmentId = #z
Set #s = #s + 1
End
Select T.EnquiryId,T.EquipmentId,T.Price
From #tab T
left join #Enquiry E on T.EnquiryId = E.EnquiryId
Where T.EquipmentId = E.EquipmentId
You can use GROUP BY (Typical way) to remove duplicate value.
Basic steps are:
Alter table & Add Identity Column.
Group by columns which can be dupicate.
Delete those record.
Check here Remove Duplicate Rows from a Table in SQL Server

Insert random Data content in SQL Server 2008

I know there are several topics on this, but none of them was suitable for me, that's why I took the chance to ask you again.
I have a table which has columns UserID, FirstName, Lastname.
I need to insert 300 000 records for each column and they have to be unique, for example:
UserID0001, John00001, Doe00001
UserID0002, John00002, Doe00002
UserID0003, John00003, Doe00003
I hope there is an easy way :)
Thank you in advance.
Best,
Lyubo
;with sequence as (
select N = row_number() over (order by ##spid)
from sys.all_columns c1, sys.all_columns c2
)
insert into [Table] (UserID, FirstName, Lastname)
select
'UserID' + right('000000' + cast(N as varchar(10)), 6),
'John' + right('000000' + cast(N as varchar(10)), 6),
'Doe' + right('000000' + cast(N as varchar(10)), 6)
from sequence where N <= 300000
You could use the ROW_NUMBER function to generate different numbers like this:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.users(
Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
user_id VARCHAR(20),
first_name VARCHAR(20),
last_name VARCHAR(20)
);
GO
DECLARE #NoOfRows INT = 7;
INSERT INTO dbo.users(user_id, first_name, last_name)
SELECT 'User_'+n, 'John_'+n, 'Doe_'+n
FROM(
SELECT REPLACE(STR(ROW_NUMBER()OVER(ORDER BY (SELECT NULL))),' ','0') n FROM(
select TOP(#NoOfRows) 1 x from sys.objects A,sys.objects B,sys.objects C,sys.objects D,sys.objects E,sys.objects F,sys.objects G
)X
)N
Query 1:
SELECT * FROM dbo.users
Results:
| ID | USER_ID | FIRST_NAME | LAST_NAME |
-----------------------------------------------------------
| 1 | User_0000000001 | John_0000000001 | Doe_0000000001 |
| 2 | User_0000000002 | John_0000000002 | Doe_0000000002 |
| 3 | User_0000000003 | John_0000000003 | Doe_0000000003 |
| 4 | User_0000000004 | John_0000000004 | Doe_0000000004 |
| 5 | User_0000000005 | John_0000000005 | Doe_0000000005 |
| 6 | User_0000000006 | John_0000000006 | Doe_0000000006 |
| 7 | User_0000000007 | John_0000000007 | Doe_0000000007 |
Just change the #NoOfRows to 300000 to get the number of rows you are looking for.
I've adapted a script found in this article:
DECLARE #RowCount INT
DECLARE #RowString VARCHAR(14)
DECLARE #First VARCHAR(14)
DECLARE #LAST VARCHAR(14)
DECLARE #ID VARCHAR(14)
SET #ID = 'UserID'
SET #First = 'John'
SET #Last = 'Doe'
SET #RowCount = 1
WHILE #RowCount < 300001
BEGIN
SET #RowString = CAST(#RowCount AS VARCHAR(10))
SET #RowString = REPLICATE('0', 6 - DATALENGTH(#RowString)) + #RowString
INSERT INTO TestTableSize (
UserID
,FirstName
,LastName
)
VALUES
(#ID + #RowString
, #First + #RowString
, #Last + #RowString)
SET #RowCount = #RowCount + 1
END

How to replace a functional (many) OUTER APPLY (SELECT * FROM)

Applies to Microsoft SQL Server 2008 R2.
The problem is
If we have a few dozen Outer Apply (30) then they begin to work pretty slowly. In the middle of the Outer Apply I have something more complicated than a simple select, a view.
Details
I'm writing a sort of attributes assigned to tables (in the database). Generally, a few tables, holds a reference to a table of attributes (key, value).
Pseudo structure looks like this:
DECLARE #Lot TABLE (
LotId INT PRIMARY KEY IDENTITY,
SomeText VARCHAR(8))
INSERT INTO #Lot
OUTPUT INSERTED.*
VALUES ('Hello'), ('World')
DECLARE #Attribute TABLE(
AttributeId INT PRIMARY KEY IDENTITY,
LotId INT,
Val VARCHAR(8),
Kind VARCHAR(8))
INSERT INTO #Attribute
OUTPUT INSERTED.* VALUES
(1, 'Foo1', 'Kind1'), (1, 'Foo2', 'Kind2'),
(2, 'Bar1', 'Kind1'), (2, 'Bar2', 'Kind2'), (2, 'Bar3', 'Kind3')
LotId SomeText
----------- --------
1 Hello
2 World
AttributeId LotId Val Kind
----------- ----------- -------- --------
1 1 Foo1 Kind1
2 1 Foo2 Kind2
3 2 Bar1 Kind1
4 2 Bar2 Kind2
5 2 Bar3 Kind3
I can now run a query such as:
SELECT
[l].[LotId]
, [SomeText]
, [Oa1].[AttributeId]
, [Oa1].[LotId]
, 'Kind1Val' = [Oa1].[Val]
, [Oa1].[Kind]
, [Oa2].[AttributeId]
, [Oa2].[LotId]
, 'Kind2Val' = [Oa2].[Val]
, [Oa2].[Kind]
, [Oa3].[AttributeId]
, [Oa3].[LotId]
, 'Kind3Val' = [Oa3].[Val]
, [Oa3].[Kind]
FROM #Lot AS l
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind1') AS Oa1
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind2') AS Oa2
OUTER APPLY(SELECT * FROM #Attribute AS la WHERE la.[LotId] = l.[LotId] AND la.[Kind] = 'Kind3') AS Oa3
LotId SomeText AttributeId LotId Kind1Val Kind AttributeId LotId Kind2Val Kind AttributeId LotId Kind3Val Kind
----------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- -------- ----------- ----------- -------- --------
1 Hello 1 1 Foo1 Kind1 2 1 Foo2 Kind2 NULL NULL NULL NULL
2 World 3 2 Bar1 Kind1 4 2 Bar2 Kind2 5 2 Bar3 Kind3
The simple way to get the pivot table of attribute values ​​and results for Lot rows that do not have attribute such a Kind3.
I know Microsoft PIVOT and it is not simple and do not fits here.
Finally, what will be faster and will give the same results?
In order to get the result you can unpivot and then pivot the data.
There are two ways that you can perform this. First, you can use the UNPIVOT and the PIVOT function:
select *
from
(
select LotId,
SomeText,
col+'_'+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from #Lot l
left join #Attribute a
on l.LotId = a.LotId
) src
unpivot
(
value
for col in (attributeid, a_Lotid, val, kind)
) unpiv
) d
pivot
(
max(value)
for col in (attributeid_1, a_LotId_1, Val_1, Kind_1,
attributeid_2, a_LotId_2, Val_2, Kind_2,
attributeid_3, a_LotId_3, Val_3, Kind_3)
) piv
See SQL Fiddle with Demo.
Or starting in SQL Server 2008+, you can use CROSS APPLY with a VALUES clause to unpivot the data:
select *
from
(
select LotId,
SomeText,
col+'_'+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from #Lot l
left join #Attribute a
on l.LotId = a.LotId
) src
cross apply
(
values ('attributeid', attributeid),('LotId', a_LotId), ('Value', Val), ('Kind', Kind)
) c (col, value)
) d
pivot
(
max(value)
for col in (attributeid_1, LotId_1, Value_1, Kind_1,
attributeid_2, LotId_2, Value_2, Kind_2,
attributeid_3, LotId_3, Value_3, Kind_3)
) piv
See SQL Fiddle with Demo.
The unpivot process takes the multiple columns for each LotID and SomeText and converts it into rows giving the result:
| LOTID | SOMETEXT | COL | VALUE |
--------------------------------------------
| 1 | Hello | attributeid_1 | 1 |
| 1 | Hello | LotId_1 | 1 |
| 1 | Hello | Value_1 | Foo1 |
| 1 | Hello | Kind_1 | Kind1 |
| 1 | Hello | attributeid_2 | 2 |
I added a row_number() to the inner subquery to be used to create the new column names to pivot. Once the names are created the pivot can be applied to the new columns giving the final result
This could also be done using dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+rn)
from
(
select
cast(ROW_NUMBER() over(partition by l.lotid order by a.attributeid) as varchar(10)) rn
from Lot l
left join Attribute a
on l.LotId = a.LotId
) t
cross apply (values ('attributeid', 1),
('LotId', 2),
('Value', 3),
('Kind', 4)) c (col, so)
group by col, rn, so
order by rn, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT LotId,
SomeText,' + #cols + '
from
(
select LotId,
SomeText,
col+''_''+CAST(rn as varchar(10)) col,
value
from
(
select l.LotId,
l.SomeText,
cast(a.AttributeId as varchar(8)) attributeid,
cast(a.LotId as varchar(8)) a_LotId,
a.Val,
a.Kind,
ROW_NUMBER() over(partition by l.lotid order by a.attributeid) rn
from Lot l
left join Attribute a
on l.LotId = a.LotId
) src
cross apply
(
values (''attributeid'', attributeid),(''LotId'', a_LotId), (''Value'', Val), (''Kind'', Kind)
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
All three versions will give the same result:
| LOTID | SOMETEXT | ATTRIBUTEID_1 | LOTID_1 | VALUE_1 | KIND_1 | ATTRIBUTEID_2 | LOTID_2 | VALUE_2 | KIND_2 | ATTRIBUTEID_3 | LOTID_3 | VALUE_3 | KIND_3 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | Hello | 1 | 1 | Foo1 | Kind1 | 2 | 1 | Foo2 | Kind2 | (null) | (null) | (null) | (null) |
| 2 | World | 3 | 2 | Bar1 | Kind1 | 4 | 2 | Bar2 | Kind2 | 5 | 2 | Bar3 | Kind3 |

Resources