Have all Records in one Field - sql

how is possible to have all records of one field into one field
Id, No , FDevice
1 , 1 , 'A'
2 , 1 , 'B'
3 , 1 , 'C'
4 , 2 , 'D'
5 , 2 , 'E'
I want to have
No , FDevice
1 , A-B-C
2 , D-E
Thank you for your help

use STUFF() - which inserts a string into another string.
SELECT
[No],
STUFF(
(SELECT '-' + [FDevice]
FROM TableName
WHERE [No] = a.[No]
FOR XML PATH (''))
, 1, 1, '') AS FDevice
FROM TableName AS a
GROUP BY [No]
SQLFiddle Demo

There're a well-known solution for aggregate concatenation in SQL Server, using select ... for xml path(''), but I have to say that many people using it incorrectly. Correct way to do this would be
select
a.[No],
stuff(
(
select '-' + t.[FDevice]
from TableName as t
where t.[No] = a.[No]
for xml path(''), type
).value('.', 'nvarchar(max)')
, 1, 1, '') as FDevice
from (select distinct [No] from TableName) as a;
sql fiddle demo
The main part is to use xml type inside the query and then to convert it into varchar using value function, otherwise you can end up with incorrectly converted special chars like '>', '<', '&' and so on. SQLfiddle somehow doesn't show the difference, but here's a script which can show you what can happen if you don't use xml type:
declare #TableName table
([Id] int, [No] int, [FDevice] varchar(3))
;
INSERT INTO #TableName
([Id], [No], [FDevice])
VALUES
(1, 1, 'A<'),
(2, 1, 'B'),
(3, 1, '&C'),
(4, 2, 'D'),
(5, 2, 'E')
;
SELECT
[No],
STUFF(
(SELECT '-' + [FDevice]
FROM #TableName
WHERE [No] = a.[No]
FOR XML PATH (''))
, 1, 1, '') AS FDevice
FROM #TableName AS a
GROUP BY [No];
outputs
No FDevice
--------------------
1 A<-B-&C
2 D-E
select
a.[No],
stuff(
(
select '-' + t.[FDevice]
from #TableName as t
where t.[No] = a.[No]
for xml path(''), type
).value('.', 'nvarchar(max)')
, 1, 1, '') as FDevice
from (select distinct [No] from #TableName) as a;
outputs
No FDevice
--------------------
1 A<-B-&C
2 D-E

Related

SQL Select FOR XML PATH - Combine ordering and concatenate string with increasing counter

I am trying to get from the following SQL table
CODE , FILENAME , ORDER
xxxx , Noimage.jpg , 0
xxxx , yyyy.jpg , 2
xxxx , zzzz.jpg , 1
xxxx , aaaa.jpg , 3
the following result --> p1=zzzz.jpg&p2=yyyy.jpg&p3=aaaa.jpg
I have tried
SELECT STUFF((SELECT '&' + FILENAME
FROM MYTABLE
WHERE IMAGEORDER<>0
FOR XML PATH('')) ,1,1,'') AS Txt
but i cant figure out how can i combine the ordering and based on ordering to contacatenate the p1 , p2 ,p3
Any help is appreciated
You just need to order the rows:
DECLARE #t TABLE (Code VARCHAR(255), Filename VARCHAR(255), ImageOrder INT)
INSERT INTO #t VALUES
('xxxx', 'Noimage.jpg', 0),
('xxxx', 'yyyy.jpg', 2),
('xxxx', 'zzzz.jpg', 1),
('xxxx', 'aaaa.jpg', 3)
SELECT REPLACE(
(SELECT CAST('&p' + CAST(ImageOrder AS varchar) + '=' + Filename AS VARCHAR(MAX))
FROM #t
WHERE ImageOrder <> 0
ORDER BY ImageOrder
FOR XML PATH (''))
, 'amp;', '') AS Txt
Which will give you the result:
&p1=zzzz.jpg&p2=yyyy.jpg&p3=aaaa.jpg
Your stuff needs meta data else the output will be formatted as xml. Also, if you are using version 2016+ you can use the STUFF() function to achieve the same functionality. I added a normalization CTE to add the calculated P1,P2..PX values based on IMAGEORDER.
;WITH Normalized AS
(
SELECT
'&P'+CAST(ROW_NUMBER() OVER (ORDER BY IMAGEORDER) AS NVARCHAR(10))+'='+FILENAME AS Item,
IMAGEORDER
FROM
MYTABLE
WHERE
IMAGEORDER<>0
)
SELECT STUFF((SELECT Item
FROM Normalized ORDER BY IMAGEORDER
FOR XML PATH(''), TYPE).value('.','varchar(max)') ,1,1,'') AS Txt

many rows into a single column with SQL

I have an one to many table, and if there is rows that have same reference id(Paragraph ID) I want to concatenate so LoginName value have many in same row.
This query does what I want it to do but there is a problem, It replaces first char. the STUFF function requires a replace value.
My question:
How can I do this without replacing first char?
SELECT DISTINCT
ParagraphID
, STUFF((
SELECT N'|' + CAST([LoginName] AS VARCHAR(255))
FROM [dbo].[CM_Signature] f2
WHERE f1.ParagraphID = f2.ParagraphID
FOR XML PATH ('')), 1, 2, '') AS FileNameString
FROM [dbo].[CM_Signature] f1
Expected value:
Daniel | Emma
Here is what you can use:
SELECT DISTINCT
ParagraphID
, STUFF((
SELECT N' | ' + CAST([LoginName] AS VARCHAR(255))
FROM [dbo].[CM_Signature] f2
WHERE f1.ParagraphID = f2.ParagraphID
FOR XML PATH ('')), 1, 1, '') AS FileNameString
FROM [dbo].[CM_Signature] f1
Note the STUFF("...", 1, 1, '') instead of STUFF("...", 1, 2, '').
Because you need to replace 1 char instead of 2 (To remove the first |).
Output:
Daniel|Emma
Also, if you want to have spaces before and after the |, just use this query:
SELECT DISTINCT
ParagraphID
, STUFF((
SELECT N' | ' + CAST([LoginName] AS VARCHAR(255))
FROM [dbo].[CM_Signature] f2
WHERE f1.ParagraphID = f2.ParagraphID
FOR XML PATH ('')), 1, 3, '') AS FileNameString
FROM [dbo].[CM_Signature] f1
Note that this time we removed 3 chars (STUFF("...", 1, 3, '')).
Output:
Daniel | Emma
You were starting your path at position 2 instead of first
SELECT DISTINCT
ParagraphID
, STUFF((
SELECT N'|' + CAST([LoginName] AS VARCHAR(255))
FROM [dbo].[CM_Signature] f2
WHERE f1.ParagraphID = f2.ParagraphID
FOR XML PATH ('')), 1, 1, '') AS FileNameString
FROM [dbo].[CM_Signature] f1 SELECT DISTINCT
ParagraphID
, STUFF((
SELECT N'|' + CAST([name] AS VARCHAR(255))
FROM mytable f2
WHERE f1.paragraphid = f2.paragraphid
FOR XML PATH ('')), 1, 1, '') AS FileNameString
FROM mytable f1
Use this Code:
create table #test (paragraghid int,name VARCHAR(10))
insert into #test values(1929,'Daniel')
insert into #test values(1929,'Emma')
insert into #test values(1935,'Daniel')
select distinct paragraghid,STUFF((select ' | ' + name from #test a
Where a.paragraghid=b.paragraghid for XML PATH('') ),1,2,'') as FilenameString
from #test b
You can write a query as:
DECLARE #CM_Signature table
(
RowId int,
ParagraphID int,
LoginName varchar(10))
Insert into #CM_Signature values
(4,1929,' Daniel'),
(5,1929,' Emma '),
(6,1935,'Daniel')
SELECT DISTINCT
ParagraphID
, STUFF((
SELECT N'| ' + CAST(rtrim(ltrim([LoginName])) AS VARCHAR(255))
FROM #CM_Signature f2
WHERE f1.ParagraphID = f2.ParagraphID
FOR XML PATH ('')), 1, 1, '') AS FileNameString
FROM #CM_Signature f1

Delete duplicate values from concatenated string

I have the following table:
Object Field Values
------------------------------------
1 1 A;A;A;B;A;A
2 1 A;B;C;C
2 2 X
3 1 X;Y;Z
3 2 V;V;V;V;V;V;V;V;V;V;V
How can I select from this table only the unique values from the concatenated values? So:
Object Field Values
---------------------
1 1 A;B
2 1 A;B;C
2 2 X
3 1 X;Y;Z
3 2 V
In any scripting language, I would loop through the values from Values, explode on ; and loop through that array with some logic filtering out duplicates. However, I need to do this only using SQL (Server 2008).
Can anybody tell me if and how this can be done?
Any help is greatly appreciated :-)
To do this first create a split function. This is the one I use but if you search the internet (or even SO) for "SQL Server Split Function" you will find a number of alternatives if you don't like this:
ALTER FUNCTION [dbo].[Split](#StringToSplit NVARCHAR(MAX), #Delimiter NCHAR(1))
RETURNS TABLE
AS
RETURN
(
SELECT ID = ROW_NUMBER() OVER(ORDER BY n.Number),
Position = Number,
Value = SUBSTRING(#StringToSplit, Number, CHARINDEX(#Delimiter, #StringToSplit + #Delimiter, Number) - Number)
FROM ( SELECT TOP (LEN(#StringToSplit) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
) n
WHERE SUBSTRING(#Delimiter + #StringToSplit + #Delimiter, n.Number, 1) = #Delimiter
);
Then you can split your field, So running:
SELECT t.Object, t.Field, s.Value
FROM T
CROSS APPLY dbo.Split(t.[Values], ';') AS s
Will turn this:
Object Field Values
------------------------------------
1 1 A;A;A;B;A;A
into:
Object Field Values
------------------------------------
1 1 A
1 1 A
1 1 A
1 1 B
1 1 A
1 1 A
Then you can apply the DISTINCT Operator:
SELECT DISTINCT t.Object, t.Field, s.Value
FROM T
CROSS APPLY dbo.Split(t.[Values], ';') AS s;
To give:
Object Field Values
------------------------------------
1 1 A
1 1 B
Then you can concatenate your rows back into a single column giving a final query:
SELECT t.Object, t.Field, [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM T
CROSS APPLY
( SELECT DISTINCT ';' + s.Value
FROM dbo.Split(t.[Values], ';') AS s
FOR XML PATH(''), TYPE
) AS s (x)
SQL Fiddle appears to be down, but once you have the Split function created the below is a full working example:
CREATE TABLE #T (Object INT, Field INT, [Values] VARCHAR(MAX));
INSERT #T
VALUES
(1, 1, 'A;A;A;B;A;A'),
(2, 1, 'A;B;C;C'),
(2, 2, 'X'),
(3, 1, 'X;Y;Z'),
(3, 2, 'V;V;V;V;V;V;V;V;V;V;V');
SELECT t.Object, t.Field, [Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM #T AS T
CROSS APPLY
( SELECT DISTINCT ';' + s.Value
FROM dbo.Split(t.[Values], ';') AS s
FOR XML PATH(''), TYPE
) AS s (x);
EDIT
Based on your comment that you can't create tables or modify the DDL, I thought I would account for the situation where you can't create a function either. You can expand the above split function out into your query, so you don't actually need to create a function:
CREATE TABLE #T (Object INT, Field INT, [Values] VARCHAR(MAX));
INSERT #T
VALUES
(1, 1, 'A;A;A;B;A;A'),
(2, 1, 'A;B;C;C'),
(2, 2, 'X'),
(3, 1, 'X;Y;Z'),
(3, 2, 'V;V;V;V;V;V;V;V;V;V;V');
SELECT t.Object,
t.Field,
[Values] = STUFF(x.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM #T AS T
CROSS APPLY
( SELECT DISTINCT ';' + SUBSTRING(t.[Values], Number, CHARINDEX(';', t.[Values] + ';', Number) - Number)
FROM ( SELECT TOP (LEN(t.[Values]) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
) n
WHERE SUBSTRING(';' + t.[Values] + ';', n.Number, 1) = ';'
FOR XML PATH(''), TYPE
) AS s (x);
Here is a standalone solution:
DECLARE #t table(Object int, Field int, [Values] varchar(max))
INSERT #t values
(1, 1, 'A;A;A;B;A;A'),
(2, 1, 'A;B;C;C'),
(3, 1, 'X'),
(4, 1, 'X;Y;Z'),
(5, 1, 'V;V;V;V;V;V;V;V;V;V;V')
SELECT t.Object, t.Field, x.[NewValues]
FROM #t t
CROSS APPLY
(
SELECT STUFF((
SELECT distinct ';'+t.c.value('.', 'VARCHAR(2000)') value
FROM (
SELECT x = CAST('<t>' +
REPLACE([Values], ';', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, '') [NewValues]
) x
Result:
Object Field NewValues
1 1 A;B
2 1 A;B;C
3 1 X
4 1 X;Y;Z
5 1 V
According to #GarethD's comment this may perform slow.
Test data:
create table #t(Object int identity(1,1), Field int, [Values] varchar(max))
INSERT #t values
(1, 'A;A;A;B;A;A'),(1, 'A;B;C;C'), (1, 'X'), (1, 'X;Y;Z'),(1, 'V;V;V;V;V;V;V;V;V;V;V')
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
insert #t select field, [values] from #t union all select field, [values] from #t union all select field, [values] from #t
Performance testing my script:
SELECT t.Object, t.Field, x.[NewValues]
FROM #t t
CROSS APPLY
(
SELECT STUFF((
SELECT distinct ';'+t.c.value('.', 'VARCHAR(2000)') value
FROM (
SELECT x = CAST('<t>' +
REPLACE([Values], ';', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)
for xml path(''), type
).value('.', 'varchar(max)'), 1, 1, '') [NewValues]
) x
Result less than 1 sec.
Performance testing Garath script
(had to edit testdata to get all rows. Identical rows were considered as 1 row):
WITH CTE AS
( SELECT DISTINCT t.Object, t.Field, s.Value
FROM #T AS T
CROSS APPLY
( SELECT ID = ROW_NUMBER() OVER(ORDER BY n.Number),
Position = Number,
Value = SUBSTRING(t.[Values], Number, CHARINDEX(';', t.[Values] + ';', Number) - Number)
FROM ( SELECT TOP (LEN(t.[Values]) + 1) Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
) n
WHERE SUBSTRING(';' + t.[Values] + ';', n.Number, 1) = ';'
) AS s
)
SELECT Object,
Field,
[Values] = STUFF((SELECT ';' + Value
FROM CTE AS T2
WHERE T2.Object = T.Object
AND T2.Field = T.Field
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)'), 1, 1, '')
FROM CTE AS T
GROUP BY Object, Field;
Result 6 seconds
If any row has null in values this script will also crash.
Just as a Scalar Value Function alternative without the CTE...
ALTER FUNCTION [SplitRemoveDupes] (
#String VARCHAR(MAX)
,#Delimiter VARCHAR(5)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #SplitLength INT
DECLARE #DedupedValues VARCHAR(MAX)
DECLARE #SplittedValues TABLE
(
OccurenceId SMALLINT IDENTITY(1,1),
SplitValue VARCHAR(200)
)
WHILE LEN(#String) > 0
BEGIN
SELECT #SplitLength = (
CASE CHARINDEX(#Delimiter, #String)
WHEN 0
THEN LEN(#String)
ELSE CHARINDEX(#Delimiter, #String) - 1
END
)
INSERT INTO #SplittedValues
SELECT SUBSTRING(#String, 1, #SplitLength)
SELECT #String = (
CASE (LEN(#String) - #SplitLength)
WHEN 0
THEN ''
ELSE RIGHT(#String, LEN(#String) - #SplitLength - 1) END)
END
SET #DedupedValues=(SELECT DISTINCT STUFF((
SELECT DISTINCT (#Delimiter + SplitValue)
FROM #SplittedValues s
ORDER BY (#Delimiter + SplitValue)
FOR XML PATH('')
), 1, 1, '') AS a
FROM #SplittedValues ss)
RETURN #DedupedValues
END
Call it inline...
SELECT Object, Field, [dbo].[SplitRemoveDupes](Values,';') From Table

concatinate all rows of a column into single value

I have table called Rule.
RuleId Name
1 A1
2 A2
3 A3
.
.
.
Now I want all the names as single result.
may be like #allnames = A1,A2,A3
Can somebody advise how to write query for this without using loops?
Thanks in advance...
Try this:
SELECT #allnames = STUFF((SELECT distinct ',' + Name
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SQL Fiddle Demo
DECLARE #names NVARCHAR(MAX)
SELECT #names = coalesce(#names + ',', '') + coalesce(Name, '')
FROM (SELECT distinct Name FROM Rule) x
print #names
Try this one -
DECLARE #temp TABLE ([RuleId] INT, Name CHAR(2))
INSERT INTO #temp([RuleId], Name)
VALUES
(1, 'A1'),
(2, 'A2'),
(3, 'A3')
DECLARE #all_names NVARCHAR(MAX)
SELECT #all_names = STUFF((
SELECT DISTINCT ',' + Name
FROM #temp
--ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SELECT #all_names
Output -
---------------
A1,A2,A3

SQL query to split a column based on hardcoded values

I have a table eg assume this setup
table MyTable has various columns Id, UserId, col1, col2 col3 including column called Stuff.
I want to output certain Columns from MyTable with a query
but i want to split the 'Stuff' column such that 2 new columns are shown in the query
I can define the categories hardcoded, im not sure how this can be represented in sql
Categoy1 = "alpha, bravo, delta, gamma';
Categoy2 = "charlie, echo, hotel';
MyTable
ID | UserID | Stuff | Other Cols....
----------------------------------------------------------
1 1 alpha
2 2 hotel
3 1 charlie
4 1 echo
5 1 gamma
6 2 bravo
7 2 delta
i want the select query to show
UserId | Category1 | Catergory2
----------------------------------------------------------
1 alpha, gamma charlie, echo
---------------------------------------------------------
2 bravo, delta hotel
----------------------------------------------------------
i.e produce 2 columns split based on whether the stuff column contains an item from category1 or category2
based on a distinct userId the categories content can be comma separated as hown above
Please can you show how this can be done
Hope this makes sense.
Thanks
You can use the xml extensions to concatenate your strings, then just hard code the categories into each subquery:
CREATE TABLE #T (ID INT, UserID INT, [Stuff] VARCHAR(300))
INSERT #T VALUES
(1, 1, 'alpha'),
(2, 2, 'hotel'),
(3, 1, 'charlie'),
(4, 1, 'echo'),
(5, 1, 'gamma'),
(6, 2, 'bravo'),
(7, 2, 'delta');
SELECT UserID,
[Category1] = STUFF(( SELECT ', ' + [Stuff]
FROM #T t2
WHERE [Stuff] IN ('alpha', 'bravo', 'delta', 'gamma')
AND t.UserID = t2.UserID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, ''),
[Category2] = STUFF(( SELECT ', ' + [Stuff]
FROM #T t2
WHERE [Stuff] IN ('charlie', 'echo', 'hotel')
AND t.UserID = t2.UserID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
FROM ( SELECT DISTINCT UserID
FROM #T
) t
Example on SQL Fiddle
You could define your categories at the start in a CTE (Categories) for improved readibility:
WITH Categories AS
( SELECT Category, Name
FROM (VALUES
(1, 'alpha'),
(1, 'bravo'),
(1, 'delta'),
(1, 'gamma'),
(2, 'charlie'),
(2, 'echo'),
(2, 'hotel')
) t (Category, Name)
), Data AS
( SELECT UserID, [Stuff], Category
FROM T
INNER JOIN Categories c
ON c.Name = T.[Stuff]
)
SELECT UserID,
[Category1] = STUFF(( SELECT ', ' + [Stuff]
FROM Data t2
WHERE Category = 1
AND t.UserID = t2.UserID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, ''),
[Category2] = STUFF(( SELECT ', ' + [Stuff]
FROM Data t2
WHERE Category = 2
AND t.UserID = t2.UserID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
FROM ( SELECT DISTINCT UserID
FROM T
) t
Example on SQL Fiddle
My try, the technique I learned from Stack Overflow!... Please check:
DECLARE #Categoy1 NVARCHAR(MAX) = 'alpha, bravo, delta, gamma',
#Categoy2 NVARCHAR(MAX) = 'charlie, echo, hotel'
SELECT
UserID,
STUFF((SELECT ', ' + display_term
FROM sys.dm_fts_parser('"'+ ',' + #Categoy1 + '"', 1033, NULL, 0) INNER JOIN
YourTable T on display_term=[Stuff]
WHERE T.UserID= x.UserID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '') Category1,
STUFF((SELECT ', ' + display_term
FROM sys.dm_fts_parser('"'+ ',' + #Categoy2 + '"', 1033, NULL, 0) INNER JOIN
YourTable T on display_term=[Stuff]
WHERE T.UserID= x.UserID
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '') Category2
FROM YourTable x
GROUP BY UserID