Split a separated value to new single column - sql-server

I want to separate the | delimited string into new column, I tried the below code but I think is not a good practice.
SELECT
REPLACE(SUBSTRING(ReferenceName, 1, CHARINDEX('|', ReferenceName)), '|', '') AS CreditCard,
ReferenceName
FROM
CTS.DBO.cts_TxSalesPayment
Expected result
Split the value based on the delimiter:
CardNumber | ExpiryDate | ApprovalCode | .... | ... | ... | ...

One way would be to use a REPLACE function to allow the column to be parsed as XML, then PIVOT the result.
For example:
DECLARE #T TABLE (ReferenceName VARCHAR(MAX));
INSERT #T VALUES ('CardNumber=asdf|ExpiryDate=1234124x|ApprovalCode=aaaaa'), ('CardNumber=zzz|ExpiryDate=123|ApprovalCode=q'), ('CardNumber=zzz|ExpiryDate=111|ApprovalCode=q2');
SELECT *
FROM
(
SELECT RN, colName = A.B.value('local-name(.)', 'varchar(max)'), colVal = A.B.value('#val', 'varchar(max)')
FROM
(
SELECT RN = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), col = CAST('<' + REPLACE(REPLACE(ReferenceName, '=', ' val='''), '|', '''/><') + ''' />' AS XML)
FROM #T
) AS T
CROSS APPLY col.nodes('*') AS A(B)
) AS T
PIVOT
(
MAX(colVal) FOR colName IN ([CardNumber], [ExpiryDate], [ApprovalCode])
) AS P;

Related

filter out all datetime and integer values from a column in SQL SERVER

I have a string
04/09/2018 06:21:38 101342 CHARLESD JOHNSON:713-269-1878 CALL WHEN WE GET A PO 06/09/2018 08:41:38 101345 KHARLESD KOHNSON:813-269-1878 CALL WHEN WE GET A PO 08/09/2018 09:41:38 10356 THARLESD TOHNSON:913-269-1878 CALL WHEN WE GET A PO
I want output like
DateTime1 | EmpID
04/09/2018 06:21:38 101342
06/09/2018 08:41:38 101345
08/09/2018 09:41:38 10356
Please help
Create the function PatternSplitLoop from this awesome article:
Splitting Strings Based on Patterns
and execute the following:
declare #tab table (string varchar(max))
insert into #tab select '04/09/2018 06:21:38 101342 CHARLESD JOHNSON:713-269-1878 CALL WHEN WE GET A PO 06/09/2018 08:41:38 101345 KHARLESD KOHNSON:813-269-1878 CALL WHEN WE GET A PO 08/09/2018 09:41:38 10356 THARLESD TOHNSON:913-269-1878 CALL WHEN WE GET A PO '
select left(item, 19) DateTime1, substring(item, 20, len(item)) EmpID
from #tab t
cross apply [dbo].[PatternSplitLoop](string, '%[0-9][0-9][/][0-9][0-9][/][0-9][0-9][0-9][0-9]%') f
where matched = 1
Output:
XML split version:
DECLARE #Val NVARCHAR(MAX) = '04/09/2018 06:21:38 101342 CHARLESD JOHNSON:713-269-1878 CALL WHEN WE GET A PO 06/09/2018 08:41:38 101345 KHARLESD KOHNSON:813-269-1878 CALL WHEN WE GET A PO 08/09/2018 09:41:38 10356 THARLESD TOHNSON:913-269-1878 CALL WHEN WE GET A PO '
SELECT #Val = '<a>' + REPLACE(#Val, ' ', '</a><a>') + '</a>';
DECLARE #Xml XML = CONVERT(XML, #Val);
DECLARE #ValTable TABLE
(
ROWNUM INT IDENTITY(1, 1),
Val NVARCHAR(MAX)
)
INSERT into #ValTable
(Val)
SELECT *
FROM
(
SELECT c.value('.', 'NVARCHAR(64)') AS Val
FROM #Xml.nodes('/a') T(c)
) a
WHERE LEN(Val) <> 0
AND (TRY_CONVERT(DATETIME, Val) IS NOT NULL OR TRY_CONVERT(TIME, Val) IS NOT NULL OR TRY_CONVERT(INT, Val) IS NOT NULL);
SELECT CONVERT(DATETIME, CONCAT(a.Val, ' ', b.Val)) AS DateTime1, c.Val AS EmpId
FROM #ValTable a
JOIN #ValTable b
--Date and Time rows, B is time row
ON TRY_CONVERT(TIME, a.Val) IS NOT NULL AND TRY_CONVERT(TIME, b.Val) IS NOT NULL AND b.ROWNUM = a.ROWNUM + 1
-- Int rows
JOIN #ValTable c
ON TRY_CONVERT(INT, c.Val) IS NOT NULL AND c.ROWNUM = a.RowNum + 2;

Could not able to insert the xml data into a table

When i try to insert the values into the table am getting the below error.
We cant remove the space in the XML because it is generated from Javascript.
How to insert the below data into the XMLdata table.
Conversion failed when converting date and/or time from character string.
This is the sample data(#bbhdn5):
341300-02-1|04/10/2018 01:18:29|04/10/2018 06:18:29|133072;
261600-01-1|04/10/2018 06:18:29|04/10/2018 11:18:29|133073;
781100-R1-1|04/10/2018 11:18:29|04/10/2018 16:18:29|133074;
Code:
create table WMC_Savexmldata
(
XML nvarchar(max)
)
Declare #bbhdn5 nvarchar(max)
set #bbhdn5='341300-02-1|04/10/2018 01:18:29|04/10/2018 06:18:29|133072; 261600-01-1|04/10/2018 06:18:29|04/10/2018 11:18:29|133073; 781100-R1-1|04/10/2018 11:18:29|04/10/2018 16:18:29|133074;'
insert into WMC_Savexmldata
select #bbhdn5
Although this question is absolutely misleading, my magic crystal ball started to blink suddenly and told me, that you might be looking for this:
Replacing the delimiters ; and | allows to transfer your CSV-string to this XML:
<x>
<y>341300-02-1</y>
<y>04/10/2018 01:18:29</y>
<y>04/10/2018 06:18:29</y>
<y>133072</y>
</x>
<x>
<y> 261600-01-1</y>
<y>04/10/2018 06:18:29</y>
<y>04/10/2018 11:18:29</y>
<y>133073</y>
</x>
<x>
<y> 781100-R1-1</y>
<y>04/10/2018 11:18:29</y>
<y>04/10/2018 16:18:29</y>
<y>133074</y>
</x>
I use this to get the data as derived table:
DECLARE #bbhdn5 NVARCHAR(MAX);
SET #bbhdn5=N'341300-02-1|04/10/2018 01:18:29|04/10/2018 06:18:29|133072; 261600-01-1|04/10/2018 06:18:29|04/10/2018 11:18:29|133073; 781100-R1-1|04/10/2018 11:18:29|04/10/2018 16:18:29|133074;';
WITH Splitted AS
(
SELECT CAST('<x><y>' + REPLACE(REPLACE(#bbhdn5,'|','</y><y>'),';','</y></x><x><y>') + '</y></x>' AS XML) AS Casted
)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNumber
,A.x.value('y[1]','nvarchar(max)') AS RowCode
,CONVERT(DATETIME,A.x.value('y[2]','nvarchar(max)'),103) AS Date1
,CONVERT(DATETIME,A.x.value('y[3]','nvarchar(max)'),103) AS Date2
,A.x.value('y[4]','int') AS SomeNumber
FROM Splitted
CROSS APPLY Casted.nodes('/x[y/text()]') AS A(x);
The result
+-----------+-------------+-------------------------+-------------------------+------------+
| RowNumber | RowCode | Date1 | Date2 | SomeNumber |
+-----------+-------------+-------------------------+-------------------------+------------+
| 1 | 341300-02-1 | 2018-10-04 01:18:29.000 | 2018-10-04 06:18:29.000 | 133072 |
+-----------+-------------+-------------------------+-------------------------+------------+
| 2 | 261600-01-1 | 2018-10-04 06:18:29.000 | 2018-10-04 11:18:29.000 | 133073 |
+-----------+-------------+-------------------------+-------------------------+------------+
| 3 | 781100-R1-1 | 2018-10-04 11:18:29.000 | 2018-10-04 16:18:29.000 | 133074 |
+-----------+-------------+-------------------------+-------------------------+------------+
UPDATE
Change the line within the CTE Splitted to this
SELECT CAST('<x><y>' + REPLACE(REPLACE(REPLACE(REPLACE(#bbhdn5,CHAR(10),' '),CHAR(13),' '),'|','</y><y>'),';','</y></x><x><y>') + '</y></x>' AS XML) AS Casted
This will replace CHAR(13) and CHAR(10) with blanks. Any odd line break within your input should disappear. Well, you might have some additional blanks in string values. But you can replace doubled blanks with single blanks again...
insert into WMC_Savexmldata
select '496200-01-1|03/31/2018 11:18:29|03/31/2018 16:18:29|133015;245000-01-1|03/31/2018
16:18:29|03/31/2018 21:18:29|133017;262100-13-1|03/31/2018 21:18:29|04/01/2018 02:18:29|133018;'
Please insert the data like this(attached). Date comes in one line and time comes in another line and then run the below query. You will be getting the date time conversion error because of the space error in between date and time. I just want to breaks between the dates. As it is coming from browser the date is coming with space in the SQL. Please help on this. Are you clear with my question.
Declare #InputSepTmp table
(
id int,
Inputs nvarchar(max)
)
insert into #InputSepTmp
Select Row_Number() over (Order By (Select null))
, LTrim(RTrim(B.i.value('(./text())1', 'varchar(max)')))
From (Select x = Cast('' + replace((Select replace(XML,';','§§Split§§') as [*] For XML Path('')),'§§Split§§','')+'' as xml).query('.')
from WMC_Savexmldata) as A
Cross Apply x.nodes('x') AS B(i)
-- select * from #InputSepTmp
--- Cursor --------------------
SET NOCOUNT ON
DECLARE #InputID varchar(200)
DECLARE cur_InputSeparator CURSOR
STATIC FOR
select id from #InputSepTmp where Inputs <> ''
OPEN cur_InputSeparator
IF ##CURSOR_ROWS > 0
BEGIN
FETCH NEXT FROM cur_InputSeparator INTO #InputID
WHILE ##Fetch_status = 0
BEGIN
DEclare #FinalInputtmp table
(
id int,
IPValues varchar(100)
)
insert into #FinalInputtmp
-- SELECT
-- Split.a.value('.', 'NVARCHAR(max)') AS String
--FROM (SELECT
-- CAST ('' + REPLACE(LTRIM(RTRIM(Inputs)), ',', '') + '' AS XML) AS String
-- from #InputSepTmp T1 where id=#InputID) AS A CROSS APPLY String.nodes ('/M') AS Split(a);
Select Row_Number() over (Order By (Select null))
, LTrim(RTrim(B.i.value('(./text())1', 'varchar(max)')))
From (Select x = Cast('' + replace((Select replace(LTRIM(RTRIM(Inputs)),'|','§§Split§§') as [*] For XML Path('')),'§§Split§§','')+'' as xml).query('.')
from #InputSepTmp where id = #InputID) as A
Cross Apply x.nodes('x') AS B(i)
--select convert(datetime,'04/12/2018 12:50:08')
insert into WMC_CriticalPath_ScheduledDtls (SWOPACKAGENO,TASKNO,SCHEDULEDSTDATE,SCHEDULEDENDDATE,TRACKID,CreatedDate,ModifiedDate)
SELECT 'adjb', MAX(CASE WHEN D.RN=1 THEN LTRIM(RTRIM(D.IPValues)) END)[task no]
,MAX(CASE WHEN D.RN=2 THEN LTRIM(RTRIM(D.IPValues)) END) [start date]
,MAX(CASE WHEN D.RN=3 THEN LTRIM(RTRIM(D.IPValues)) END) [end date]
,MAX(CASE WHEN D.RN=4 THEN LTRIM(RTRIM(D.IPValues)) END) [id], Getdate(),NULL
FROM(
SELECT *
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL))RN
FROM #FinalInputtmp
)D
delete from #FinalInputtmp
FETCH NEXT FROM cur_InputSeparator INTO #InputID
END
END
CLOSE cur_InputSeparator
DEALLOCATE cur_InputSeparator
SET NOCOUNT OFF
--select * from WMC_CriticalPath_ScheduledDtls
select * from WMC_CriticalPath_ScheduledDtls

count and remove individual values of a single column

I have a table and the values like this
create table items_table(url varchar(max),counttotal_urls int,countduplicate_urls int,Unique_urls varchar(max),
countUnique_urls int)
insert into items_table(url) values('ht,ha,hb,ha|hc|hy')
insert into items_table(url) values('ht,hb,hb|hb|hx|hx')
insert into items_table(url) values('hz,hy,hx,hm|hm,hy')
insert into items_table(url) values('hz,hy,hx,hm|hm,hy')
I need to replace ,h with |h , for this I will use replace(url,',h','|h')
I need to count total_urls present by considering the separation '|'
I want to check or count how may duplicate url's are present
I want unique_urls to be updated in the Unique_url's column by removing the duplicates
Finally I want to count the Unique_urls which will be updated in another column
Desired output:
This is a bit complicated. But I tried to achieve it in Set based methodology.
Your Schema:
CREATE TABLE #items_table (
id INT identity
,url VARCHAR(max)
,counttotal_urls INT
,countduplicate_urls INT
,Unique_urls VARCHAR(max)
,countUnique_urls INT
)
INSERT INTO #items_table (url)
VALUES ('ht,ha,hb,ha|hc|hy')
INSERT INTO #items_table (url)
VALUES ('ht,hb,hb|hb|hx|hx')
INSERT INTO #items_table (url)
VALUES ('hz,hy,hx,hm|hm,hy')
INSERT INTO #items_table (url)
VALUES ('hy,hx,hm|hm,hy')
I have used several CTE's and XML methods
;WITH CTE
AS (
SELECT url
,REPLACE(',' + url, ',h', '|h') AS url2
,CAST('<M>'
+ REPLACE(REPLACE(',' + url, ',h', '|h'), '|', '</M><M>')
+ '</M>' AS XML) AS XML_FLD
FROM #items_table
)
,CTE2
AS (
SELECT url
,SUM(CASE
WHEN SUBSTRING(url2, number, 1) > '|'
THEN 1
ELSE 0
END) / 2 AS counttotal_urls
FROM CTE C
CROSS APPLY (
SELECT *
FROM master.dbo.spt_values
WHERE type = 'P'
AND number BETWEEN 1
AND LEN(C.url2)
) CA
GROUP BY url
)
,CTE3
AS (
SELECT C2.url
,C2.counttotal_urls
,SPLITS.ABC.value('.', 'varchar(MAX)') DUP_URLS
FROM CTE2 C2
INNER JOIN CTE C ON C2.url = C.url
CROSS APPLY C.XML_FLD.nodes('/M') AS SPLITS(ABC)
)
SELECT url
,counttotal_urls
,counttotal_urls - (COUNT(DISTINCT DUP_URLS) - 1) AS countduplicate_urls
,STUFF((
SELECT DISTINCT '|' + DUP_URLS
FROM CTE3 C
WHERE C3.url = C.url
FOR XML PATH('')
), 1, 1, '') AS Unique_urls
FROM CTE3 C3
GROUP BY url
,counttotal_urls
Result will be
+-------------------+-----------------+---------------------+-----------------+
| url | counttotal_urls | countduplicate_urls | Unique_urls |
+-------------------+-----------------+---------------------+-----------------+
| ht,ha,hb,ha|hc|hy | 6 | 1 | |ha|hb|hc|ht|hy |
| ht,hb,hb|hb|hx|hx | 6 | 3 | |hb|ht|hx |
| hy,hx,hm|hm,hy | 5 | 2 | |hm|hx|hy |
| hz,hy,hx,hm|hm,hy | 6 | 2 | |hm|hx|hy|hz |
+-------------------+-----------------+---------------------+-----------------+
You will have to create a string split table value function (Found one at aspsnippets)
as below
CREATE FUNCTION ufn_SplitString
(
#Input NVARCHAR(MAX),
#Character CHAR(1)
)
RETURNS #Output TABLE (
Item NVARCHAR(1000)
)
AS
BEGIN
DECLARE #StartIndex INT, #EndIndex INT
SET #StartIndex = 1
IF SUBSTRING(#Input, LEN(#Input) - 1, LEN(#Input)) <> #Character
BEGIN
SET #Input = #Input + #Character
END
WHILE CHARINDEX(#Character, #Input) > 0
BEGIN
SET #EndIndex = CHARINDEX(#Character, #Input)
INSERT INTO #Output(Item)
SELECT SUBSTRING(#Input, #StartIndex, #EndIndex - 1)
SET #Input = SUBSTRING(#Input, #EndIndex + 1, LEN(#Input))
END
RETURN
END
GO
Once the Function is in place the below code can be used to achieve the desired result
;WITH cte_OriginalTable(url) as
(
SELECT 'ht,ha,hb,ha|hc|hy' UNION ALL
SELECT 'ht,hb,hb|hb|hx|hx' UNION ALL
SELECT 'hz,hy,hx,hm|hm,hy' UNION ALL
SELECT 'hz,hy,hx,hm|hm,hy'
)
,cte_SaperaterFix AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS ID, replace(url, ',h', '|h') AS url
FROM cte_OriginalTable
)
,cte_Split as
(
SELECT o.*,
y.Item
FROM cte_SaperaterFix o
CROSS APPLY dbo.ufn_SplitString(o.url, '|') y
)
,cte_TotalCount AS
(
SELECT ID,COUNT(ID) AS counttotal_urls, COUNT(DISTINCT Item) AS Unique_urls, COUNT(ID) - COUNT(DISTINCT Item) AS countduplicate_urls
FROM cte_Split
GROUP BY ID
)
SELECT DISTINCT b.ID, b.url AS URLs, a.CountTotal_URLs, a.CountDuplicate_URLS, STUFF(( SELECT DISTINCT '|' + b1.Item AS [text()]
FROM cte_Split b1
WHERE
b.ID = b1.ID
FOR XML PATH('')
), 1, 1, '' ) AS Unique_URLs, a.Unique_URLs AS CountUnique_URLs
FROM cte_TotalCount a
JOIN cte_Split b
ON a.ID = b.ID

Split Data and transforming them into Columns

I have an Input table as under
Id Data
1 Column1: Value1
2 Column2: Value11
3 Column3: Value111
4 Column1: Value2
5 Column2: Value22
6 Column3: Value222
I am looking for an output as under
Column1 Column2 Column3
Value1 Value11 Value111
Value2 Value22 Value222
How can I achieve so? It could have been done easily by using a WHILE LOOP and by a bit of mathematical logic, but I am looking for a more optimized one if possible by only SELECT queries (no LOOPS).
I have tried also by splitting using (':') as delimiter and then transforming ROWS to COLUMNS (PIVOT) but somewhat could not be able to proceed. (That's my thought, peoples may have more better thoughts).
My shot so far
Declare #t table(Id int identity(1,1),Data varchar(1000))
Insert into #t Values
('Column1: Value1'),('Column2: Value11'),('Column3: Value111')
,('Column1: Value2'),('Column2: Value22'),('Column3: Value222')
Select *
FROM #t
SELECT
F1.id,
F1.Data,
O.splitdata
FROM
(
SELECT *,
cast('<X>'+replace(F.Data,':','</X><X>')+'</X>' as XML) as xmlfilter from #t F
)F1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') as splitdata
FROM f1.xmlfilter.nodes('X') as fdata(D)) O
This will work if you want a pure SQL solution:
Select [Column1], [Column2], [Column3] From (
Select col, val, id = ROW_NUMBER() over(partition by d.col order by d.id)
From (
Select id
, col = LEFT(Data, CHARINDEX(':', Data)-1)
, val = RIGHT(Data, LEN(DATA) - CHARINDEX(':', Data))
From #t
) as d
) as p
pivot(
MAX(val)
FOR col in([Column1], [Column2], [Column3])
) as piv
But it supposes that data for Row 1 are always before data for Row 2. There is no way to distinguish them using your sample.
If the number of column is not fixed, it has to use Dynamic SQL.
SQL Server may not be the best options for this kind of thing.
With Dynamic SQL, the above query would be like this one:
create table #t(Id int identity(1,1),Data varchar(1000))
Insert into #t Values
('Column1: Value1'),('Column2: Value11'),('Column3: Value111')
,('Column1: Value2'),('Column2: Value22'),('Column3: Value222')
Declare #sql nvarchar(max)
Select #sql = '
Select '+left(c, len(c)-1)+' From (
Select col, val, id = ROW_NUMBER() over(partition by d.col order by d.id)
From (
Select id
, col = LEFT(Data, CHARINDEX('':'', Data)-1)
, val = RIGHT(Data, LEN(DATA) - CHARINDEX('':'', Data))
From #t
) as d
) as p
pivot(
MAX(val)
FOR col in('+left(c, len(c)-1)+')
) as piv
'
From (
Select Distinct '['+LEFT(Data, CHARINDEX(':', Data)-1)+'], '
From #t
FOR XML PATH('')
) as d(c)
EXEC sp_executesql #sql
SQL Fiddle
This should work:
Declare #t table(Id int identity(1,1),Data varchar(1000))
Insert into #t Values
('Column1: Value1'),('Column2: Value11'),('Column3: Value111')
,('Column1: Value2'),('Column2: Value22'),('Column3: Value222');
WITH Splitted AS
(
SELECT *
,CAST('<X>'+REPLACE(F.Data,':','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM #t AS F
)
SELECT p.*
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY xmlfilter.value('X[1]','varchar(max)') ORDER BY Id) AS Inx
,xmlfilter.value('X[1]','varchar(max)') AS ColName
,xmlfilter.value('X[2]','varchar(max)') AS ColVal
FROM Splitted
) AS tbl
PIVOT
(
MAX(ColVal) FOR ColName IN(Column1,Column2,Column3)
) AS p

SQL Pivot table without aggregate

I have a number of text files that are in a format similar to what is shown below.
ENTRY,1,000000,Widget 4000,1,,,2,,
FIELD,Type,A
FIELD,Component,Widget 4000
FIELD,Vendor,Acme
ENTRY,2,000000,PRODUCT XYZ,1,,,3,
FIELD,Type,B
FIELD,ItemAssembly,ABCD
FIELD,Component,Product XYZ - 123
FIELD,Description1,Product
FIELD,Description2,XYZ-123
FIELD,Description3,Alternate Part #440
FIELD,Vendor,Contoso
They have been imported into a table with VARCHAR(MAX) as the only field. Each ENTRY is a "new" item, and all the subsequent FIELD rows are properties of that item. The data next to the FIELD is the column name of the property. The data to the right of the property is the data I want to display.
The desired output would be:
ENTRY Type Component Vendor ItemAssembly Description1
1,000000,Widget 4000 A Widget 4000 Acme
2,000000,Product XYZ B Product XYZ-123 Contoso ABCD Product
I've got the column names using the code below (there are several tables that I have UNIONed together to list all the property names).
select #cols =
STUFF (
(select Distinct ', ' + QUOTENAME(ColName) from
(SELECT
SUBSTRING(ltrim(textFileData),CHARINDEX(',', textFileData, 1)+1,CHARINDEX(',', textFileData, CHARINDEX(',', textFileData, 1)+1)- CHARINDEX(',', textFileData, 1)-1) as ColName
FROM [MyDatabase].[dbo].[MyTextFile]
where
(LEFT(textFileData,7) LIKE #c)
UNION
....
) A
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
Is a Pivot table the best way to do this? No aggregation is needed. Is there a better way to accomplish this? I want to list out data next to the FIELD name in a column format.
Thanks!
Here is the solution in SQL fiddle:
http://sqlfiddle.com/#!3/8f0b0/8
Prepare raw data in format (entry, field, value), use dynamic SQL to make pivot on unknown column count.
MAX() for string is enough to simulate "without aggregate" behavior in this case.
create table t(data varchar(max))
insert into t values('ENTRY,1,000000,Widget 4000,1,,,2,,')
insert into t values('FIELD,Type,A')
insert into t values('FIELD,Component,Widget 4000')
insert into t values('FIELD,Vendor,Acme ')
insert into t values('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
insert into t values('FIELD,Type,B')
insert into t values('FIELD,ItemAssembly,ABCD')
insert into t values('FIELD,Component,Product XYZ - 123')
insert into t values('FIELD,Description1,Product ')
insert into t values('FIELD,Description2,XYZ-123 ')
insert into t values('FIELD,Description3,Alternate Part #440')
insert into t values('FIELD,Vendor,Contoso');
create type preparedtype as table (entry varchar(max), field varchar(max), value varchar(max))
declare #prepared preparedtype
;with identified as
(
select
row_number ( ) over (order by (select 1)) as id,
substring(data, 1, charindex(',', data) - 1) as type,
substring(data, charindex(',', data) + 1, len(data)) as data
from t
)
, tree as
(
select
id,
(select max(id)
from identified
where type = 'ENTRY'
and id <= i.id) as parentid,
type,
data
from identified as i
)
, pivotsrc as
(
select
p.data as entry,
substring(c.data, 1, charindex(',', c.data) - 1) as field,
substring(c.data, charindex(',', c.data) + 1, len(c.data)) as value
from tree as p
inner join tree as c on c.parentid = p.id
where p.id = p.parentid
and c.parentid <> c.id
)
insert into #prepared
select * from pivotsrc
declare #dynamicPivotQuery as nvarchar(max)
declare #columnName as nvarchar(max)
select #columnName = ISNULL(#ColumnName + ',','')
+ QUOTENAME(field)
from (select distinct field from #prepared) AS fields
set #dynamicPivotQuery = N'select * from #prepared
pivot (max(value) for field in (' + #columnName + ')) as result'
exec sp_executesql #DynamicPivotQuery, N'#prepared preparedtype readonly', #prepared
Here your are, this comes back exactly as you need it. I love tricky SQL :-). This is a real ad-hoc singel-statement call.
DECLARE #tbl TABLE(OneCol VARCHAR(MAX));
INSERT INTO #tbl
VALUES('ENTRY,1,000000,Widget 4000,1,,,2,,')
,('FIELD,Type,A')
,('FIELD,Component,Widget 4000')
,('FIELD,Vendor,Acme ')
,('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
,('FIELD,Type,B')
,('FIELD,ItemAssembly,ABCD')
,('FIELD,Component,Product XYZ - 123')
,('FIELD,Description1,Product ')
,('FIELD,Description2,XYZ-123 ')
,('FIELD,Description3,Alternate Part #440')
,('FIELD,Vendor,Contoso');
WITH OneColumn AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS inx
,CAST('<root><r>' + REPLACE(OneCol,',','</r><r>') + '</r></root>' AS XML) AS Split
FROM #tbl AS tbl
)
,AsParts AS
(
SELECT inx
,Each.part.value('/root[1]/r[1]','varchar(max)') AS Part1
,Each.part.value('/root[1]/r[2]','varchar(max)') AS Part2
,Each.part.value('/root[1]/r[3]','varchar(max)') AS Part3
,Each.part.value('/root[1]/r[4]','varchar(max)') AS Part4
,Each.part.value('/root[1]/r[5]','varchar(max)') AS Part5
FROM OneColumn
CROSS APPLY Split.nodes('/root') AS Each(part)
)
,TheEntries AS
(
SELECT DISTINCT *
FROM AsParts
WHERE Part1='ENTRY'
)
SELECT TheEntries.Part2 + ',' + TheEntries.Part3 + ',' + TheEntries.Part4 AS [ENTRY]
,MyFields.AsXML.value('(fields[1]/field[Part2="Type"])[1]/Part3[1]','varchar(max)') AS [Type]
,MyFields.AsXML.value('(fields[1]/field[Part2="Component"])[1]/Part3[1]','varchar(max)') AS Component
,MyFields.AsXML.value('(fields[1]/field[Part2="Vendor"])[1]/Part3[1]','varchar(max)') AS Vendor
,MyFields.AsXML.value('(fields[1]/field[Part2="ItemAssembly"])[1]/Part3[1]','varchar(max)') AS ItemAssembly
,MyFields.AsXML.value('(fields[1]/field[Part2="Description1"])[1]/Part3[1]','varchar(max)') AS Description1
FROM TheEntries
CROSS APPLY
(
SELECT *
FROM AsParts AS ap
WHERE ap.Part1='FIELD' AND ap.inx>TheEntries.inx
AND ap.inx < ISNULL((SELECT TOP 1 nextEntry.inx FROM TheEntries AS nextEntry WHERE nextEntry.inx>TheEntries.inx ORDER BY nextEntry.inx DESC),10000000)
ORDER BY ap.inx
FOR XML PATH('field'), ROOT('fields'),TYPE
) AS MyFields(AsXML)

Resources