I am trying to take one column in my table that has multiple values (up to 10) delimited by a "pipe" "|" in it and add the delimited values into additional columns in the table. Note (running on SQL SVR 2014).
Table ...
Col1 Col2 Col3
1 Tom 12345678|87654321|11111111|22222222|..... up to 10
2 Joe 14563467
3 Zac 12345678|87654321
I need the results of SQL to produce
Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 ....
1 Tom 12345678 87654321 11111111 22222222
2 Joe 14563467
3 Zac 12345678 87654321
Any help is appreciated!
You can first normalize your data using the new STRING_SPLIT function into a derived table with the split columns stretched downward. Using that table you can PIVOT out based on Col1 to create the 5 columns basically pulling the data back up that was previously split down. Next, use that data as the source for you update back to the source table.
If you are not on SQL Server 2016 then you will need to replace STRING_SPLIT with an delimited string parser Table Value Function .
DECLARE #T TABLE(Col1 INT, Col2 NVARCHAR(50), Col3 NVARCHAR(50), Col4 NVARCHAR(50), Col5 NVARCHAR(50))
INSERT #T (COl1,Col2) VALUES (1,'12345678|87654321|11111111|22222222|')
INSERT #T (COl1,Col2) VALUES (2,'12345678')
INSERT #T (COl1,Col2) VALUES (3,'12345678|87654321|')
SELECT * FROM #T
;
WITH SplitData AS
(
SELECT
Col1,Col2 = S.Value,
RN = ROW_NUMBER()OVER( PARTITION BY Col1 ORDER BY (SELECT 1 AS X))
FROM
#T
CROSS APPLY STRING_SPLIT(Col2,'|') S
)
,UpdateData AS
(
SELECT
Col1, Col2=[1], Col3=[2], Col4=[3], Col5=[4]
FROM
(
SELECT Col1, Col2, RN FROM SplitData
) AS S
PIVOT(
MAX(Col2) FOR RN IN ([1], [2], [3], [4], [5])
) AS P
)
UPDATE L
SET L.Col1 = R.Col1, L.Col2=R.Col2, L.Col3=R.Col3, L.Col4=R.Col4, L.Col5 = R.Col5
FROM
#T L
INNER JOIN UpdateData R ON L.Col1 = R.Col1
SELECT * FROM #T
If u are sure max values count in Col3 is 10 u can use cross apply with split string on Col3 and select columns with splited value and row number partitioned by col1 and col2. Secound step is pivot this table by row
WITH SplitTable AS(
SELECT Col1
,Col2
,value
,ROW_NUMBER() OVER(PARTITION BY Col1, Col2 ORDER BY (SELECT NULL)) as rn
FROM YOUR_TABLE
CROSS APPLY STRING_SPLIT(Col3, '|') AS YT
)
SELECT Col1
,Col2
,[1]
,[2]
,[3]
,[4]
,[5]
,[6]
,[7]
,[8]
,[9]
,[10]
FROM SplitTable
PIVOT(
MAX(VALUE)
FOR RN IN([1],[2],[3],[4],[5],[6],[7],[8],[9],[10])
) as PVT
Related
I have a single row query returning data in this format:
Col1 Col2 Col3 Col4
-----------------------------
1425 3454 2345 3243
I want it to display it in this format:
Col1 | 1425
Col2 | 3454
Col3 | 2345
Col4 | 3243
How can I do it?
I am avoiding to use UNION method since the above table is extracted from a query and for each <table_name> I would have to paste the table query which will make the process slow.
If the number of fields per table is always constant, then it might work like this.
DECLARE #Table TABLE(
[Col1] int,
[Col2] int,
[Col3] int,
[Col4] int
)
INSERT INTO #Table VALUES(1425, 3454, 2345, 3243); -- some Test data
SELECT * FROM #Table; -- row
SELECT
p.[Columns],
p.[Value]
FROM (
SELECT
[Col1],
[Col2],
[Col3],
[Col4]
FROM #Table
) x
UNPIVOT(
[Value] FOR [Columns] IN ([Col1],[Col2],[Col3],[Col4]) --
) AS P;
You can cross join your query with the column names in order to show the column values in separate rows:
select
columns.col,
case columns.col
when 'Col1' then q.col1
when 'Col2' then q.col2
when 'Col3' then q.col3
when 'Col4' then q.col4
end as value
from ( <your query here> ) q
cross join ( values ('Col1'), ('Col2'), ('Col3'), ('Col4') ) as columns(col);
I have two tables with n of columns from Col1 to Col30
Table 1.
Templateid Col1 Col2 Col3 Col4 ...
95 2019-05-28 1234 test123 123456
Table 2.
Templateid DisplayName ColumnName
95 date col1
95 rank col2
95 purpose col3
95 sign col4
Expected Results.
Col1Name Col1Value Col2Name Col2Value Col3Name Col3Value ....
date 2019-05-28 rank 1234 purpose test123
This is a crude way of doing it and if you do not know the number of columns in each table you would need to use dynamic sql to enumerate them out but for the purposes of this example I have assumed you do know the number of columns and the names you want to populate.
The union query allows you to pre-populate the desired column names using the col1 syntax, then the pivot allows you to match up the displaynames and the display values. A case statement is required to ensure the correct values are shown and you do need to populate your derived column names for the pivot query but you do get the desired outcome this way.
declare #table1 table (
Templateid int,
Col1 date,
col2 int,
col3 nvarchar(10),
col4 int
);
insert into #table1 (Templateid, col1, col2, col3, col4)
values
(95, '2019-05-28', '1234', 'test123', '123456');
declare #table2 table (
Templateid int,
Displayname nvarchar(10),
ColumnName nvarchar(10)
);
insert into #table2 (Templateid, Displayname, ColumnName)
values
(95, 'date', 'col1'),
(95, 'rank', 'col2'),
(95, 'purpose', 'col3'),
(95, 'sign', 'col4');
select * from
(
select columnname+'Name' as columnname, Displayname
from #table2 t2
union
select columnname+'Value', case when columnname='col1' then cast(col1 as nvarchar(15))
when columnname='col2' then cast(col2 as nvarchar(15))
when columnname='col3' then cast(col3 as nvarchar(15))
when columnname='col4' then cast(col4 as nvarchar(15)) end
from #table1 t1 inner join #table2 t2 on t1.Templateid=t2.Templateid) src
pivot
(max(displayname) for columnname in ([col1Name],[col1Value], [col2Name],[col2Value], [col3Name],[col3Value], [col4Name],[col4Value])) piv;
In SQL Server, I have a table like this:
Col-1 Col-2 Col-3
------------------------
A 123 25.13
A 456 67.00
A 789 81.89
I want output like this:
Col-1 Col-2 Col-3
-----------------------------------
A 123,456,789 174.02
You can use FOR XML clause with STUFF() function :
select co1, stuff( (select distinct ','+cast(t1.col2 as varchar(255))
from table t1
where t1.col1 = t.col1
for xml path('')
), 1, 1, ''
) as col2,
sum(col3)
from table t
group by co1;
Sample Data :
DECLARE #T TABLE(Col1 VARCHAR(1),
Col2 INT,
Col3 FLOAT)
INSERT INTO #T( Col1, Col2, Col3 )
VALUES ('A',123,25.13),('A',456,67),('A',789,81.89)
Query :
;WITH Keys AS (SELECT Col1,
SUM(Col3) AS Col3
FROM #T
GROUP BY Col1)
SELECT Keys.Col1,
REPLACE(REPLACE(REPLACE( CONVERT (NVARCHAR(MAX),(SELECT T.Col2 AS A
FROM #T AS T
WHERE T.Col1 = Keys.Col1
FOR XML PATH(''))),'</A><A>',','),'<A>',''),'</A>','') AS Col2,
Keys.Col3
FROM Keys
Result :
Col1 Col2 Col3
A 123,456,789 174.02
I have some data as under
Declare #t table (Id int identity,CommaSeperatedValue varchar(100))
Insert Into #t
Select 'Somalia,Vietnam' Union All
Select 'apple,banana,guava,India,Australia'
There is no limit in the CommaSeperated value. The desired output for the sample provided will be
Id Col1 Col2 Col3 Col4 Col5
1 Somalia Vietnam Null Null Null
2 apple banana guava India Australia
That means , the columns will be generated dynamically. Let us take another example
Declare #t table (Id int identity,CommaSeperatedValue varchar(100))
Insert Into #t
Select 'Somalia,Vietnam,Honolulu,Spain' Union All
Select 'apple,banana,guava,India,Australia,Smart,Bus' Union All
Select 'Mango'
The desired output
Id Col1 Col2 Col3 Col4 Col5 Col6 Col7
1 Somalia Vietnam Honolulu Spain Null Null Null
2 apple banana guava India Australia Smart Bus
3 Mango Null Null Null Null Null Null
How to do this query?
My attempt so far(after this I am lost)
SELECT
X.id,
X.CommaSeperatedValue,
Y.splitdata
FROM
(
SELECT *,
CAST('<X>'+REPLACE(F.CommaSeperatedValue,',','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM #t F
)X
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') as splitdata
FROM X.xmlfilter.nodes('X') as fdata(D)
)Y
Thanks in advance
Well here is the Dynamic Solution you are looking for .I used Temp Table you can replace it with Permanent Table or Table Variable.
Declare #t table (Id int identity,CommaSeperatedValue varchar(100))
Insert Into #t
Select 'Somalia,Vietnam' Union All
Select 'apple,banana,guava,India,Australia'
IF object_ID('TempDB..#Temp') IS NOT NULL DROP TABLE #Temp;
SELECT
X.id,
--X.CommaSeperatedValue,
Y.splitdata
,ROW_NUMBER() OVER( PARTITION BY X.id ORDER BY X.id ) AS DataID
INTO #Temp
FROM
(
SELECT *,
CAST('<X>'+REPLACE(F.CommaSeperatedValue,',','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM #t F
)X
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') as splitdata
FROM X.xmlfilter.nodes('X') as fdata(D)
)Y
DECLARE #MAXCol INT = (SELECT MAX(DataID)FROM #Temp)
,#index INT =1
,#ColNames varchar(4000)=''
WHILE (#index<=#MAXCol)
BEGIN
SET #ColNames =#ColNames +'MAX(CASE WHEN DataID = '+LTRIM(STR(#index))+' THEN splitdata END) as Col'+LTRIM(STR(#index))+','
SET #Index=#Index +1
END
SET #ColNames = LEFT(#ColNames,LEN(#ColNames)-1) -- Remove Last Comma
EXECUTE ( 'SELECT
[id],'+#ColNames+' FROM #Temp GROUP BY [id]'
)
Going with what you already got, add a rownumber, and transpose it using a group by.
SELECT
[id],
MAX(CASE WHEN RowNumber=1 THEN splitdata END) as Col1,
MAX(CASE WHEN RowNumber=2 THEN splitdata END) as Col2,
MAX(CASE WHEN RowNumber=3 THEN splitdata END) as Col3,
MAX(CASE WHEN RowNumber=4 THEN splitdata END) as Col4,
MAX(CASE WHEN RowNumber=5 THEN splitdata END) as Col5,
MAX(CASE WHEN RowNumber=6 THEN splitdata END) as Col6,
MAX(CASE WHEN RowNumber=7 THEN splitdata END) as Col7,
MAX(CASE WHEN RowNumber=8 THEN splitdata END) as Col8,
MAX(CASE WHEN RowNumber=9 THEN splitdata END) as Col9,
MAX(CASE WHEN RowNumber=10 THEN splitdata END) as Col10
FROM (
SELECT
X.id,
Y.splitdata,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS RowNumber
FROM
(
SELECT *,
CAST('<X>'+REPLACE(F.CommaSeperatedValue,',','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM #t F
)X
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') as splitdata
FROM X.xmlfilter.nodes('X') as fdata(D)
)Y
) X
GROUP BY [id]
This will yield:
id Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8 Col9 Col10
1 Somalia Vietnam Honolulu Spain NULL NULL NULL NULL NULL NULL
2 apple banana guava India Australia Smart Bus NULL NULL NULL
3 Mango NULL NULL NULL NULL NULL NULL NULL NULL NULL
I believe you would need to use dynamic SQL to do this, because the SELECT statement needs to specify the number of columns, or you need to iterate over the number of items in the CSV column.
Typically this is a bad idea, will cause more problems than it solves, and is too confusing for the next person to maintain. What you might want to consider instead is simply flattening your data to a two columned format
-- ie your initial data
1, 'Somalia,Vietnam,Honolulu,Spain'
2, 'apple,banana,guava,India,Australia,Smart,Bus'
3, 'Mango'
-- would become
1, 'Somalia'
1, 'Vietnam'
1, 'Honolulu'
1, 'Spain'
2, 'apple'
2, 'banana
-- etc
Now group and pivot in your presentation layer.
Trying to format data in SQL (probably for a report or data export?) is a common mistake, really just one up from trying to store dates as literals. SQL is a data storage and manipulation language / platform; it is not for data Trying to use it in this manner will simply cause yourself no end of headache.
For Example, Table with three cols and data
col1 col2 col3
10 20 30
40 50 60
40 50 80
Want to do a select where for the last two rows only returns one since col1 and col2 are the same.
select distinct will not work since col3 are different.
so output would be
10 20 30
40 50 xx (don't care)
As you only have one additional column you can just use an arbitrary MIN/MAX aggregate and GROUP BY
SELECT col1,
col2,
MAX(col3) AS col3
FROM YourTable
GROUP BY col1,
col2
More generally if your RDBMS supports analytic functions you can use
WITH T
AS (SELECT col1,
col2,
col3,
ROW_NUMBER() OVER (PARTITION BY col1, col2
ORDER BY col1, col2) AS RN
FROM YourTable)
SELECT col1,
col2,
col3
FROM T
WHERE RN = 1
I did this in SQL Server:
-- Setup test data:
declare #table table (
col1 int,
col2 int,
col3 int
)
insert into #table values (10, 20, 30)
insert into #table values (40, 50, 60)
insert into #table values (40, 50, 80)
-- Here's the query:
select col1, col2, cast(min(col3) as varchar(10)) as col3
from #table
group by col1, col2
having count(*) = 1
union all
select col1, col2, 'xx' as col3
from #table
group by col1, col2
having count(*) > 1
I suppose this assumes that you have no duplicate rows (where all fields are duplicates), otherwise you'd have a possibly incorrect 'xx'.