need help in forming sql query - sql-server

ItemId Name parentId
1 A null
2 b null
3 c 1
4 d 2
5 e 3
6 f 4
7 g 2
hi i need help in create sql query. I have a table that contain 3 column itemid ,name ,parentitemid. i need a sql query that result parent child relation.if parentitemid id null then it means root .please help
i need data like.
<1><3><5></5> </3></1>

For example you can use:
WITH HierarchicalTable
AS
(
SELECT Id, ParentId, Name, 0 as [Level]
FROM YourTable
WHERE ParentId IS NULL
UNION ALL
SELECT YourTable.Id, YourTable.ParentId, YourTable.Name, [Level] + 1
FROM YourTable
JOIN HierarchicalTable ON HierarchicalTable.Id = YourTable.ParentId
)
SELECT [Level], Name FROM HierarchicalTable

This is excessively complicated solution, but it will work for your problem:
DECLARE #temp TABLE (ItemId int, Name char(1), parentId int,l int)
DECLARE #xml TABLE (s nvarchar(max), e nvarchar(max), parentId int, itemid int)
DECLARE #l int
;WITH cte AS (
SELECT *
FROM (VALUES
(1, 'a', NULL),(2, 'b', NULL),(3, 'c', 1),(4, 'd', 2),(5, 'e', 3),(6, 'f', 4),(7, 'g', 2)
) as t(ItemId, Name, parentId)
--Here we create recursive cte to obtain levels of nesting
), res AS (
SELECT *,
1 [Level]
FROM cte c
where parentId IS null
UNION ALL
SELECT c.*,
[Level]+1
FROM res r
INNER JOIN cte c
ON c.parentId = r.ItemId
)
--put results into temp table
INSERT INTO #temp
SELECT *
FROM res
--obtain max level
SELECT #l = MAX(l)
FROM #temp
--from max level to 1 begin
WHILE #l > 0
BEGIN
--if there is nodes with same parentid - concatinating them
UPDATE x
SET x.e = x.e + v.s + v.e
FROM #xml x
INNER JOIN #xml v
ON v.parentId = x.parentId and v.e !=x.e;
--here we merge table with results
-- first run <e></e>
-- next run <c><e></e></c>
-- next run <a><c><e></e></c></a>
MERGE #xml AS target
USING (
SELECT '<'+ Name +'>' as s,'</'+ Name + '>' as e, parentId, ItemId
FROM #temp
WHERE l = #l
) as source
ON target.parentid = source.itemid
WHEN NOT MATCHED THEN INSERT VALUES (source.s, source.e, source.parentId, source.ItemId)
WHEN MATCHED THEN
UPDATE
SET target.s = source.s + target.s,
target.e = target.e + source.e,
target.parentid = source.parentid,
target.itemid = source.itemid;
--next level down
SET #l = #l - 1
END
SELECT x --CAST(x as xml)
FROM (
SELECT s+e as x,
DENSE_RANK() OVER (PARTITION BY itemid ORDER BY s ASC) as rn
--need that column to obtain first one of every string for itemid
FROM #xml
) as c
WHERE c.rn = 1
--FOR XML PATH ('')
Output will be:
x
<a><c><e></e></c></a>
<b><d><f></f></d><g></g></b>
If you remove -- near FOR XML PATH ('') and change this SELECT x --CAST(x as xml) to this SELECT CAST(x as xml) in last query you will get this:
<a>
<c>
<e />
</c>
</a>
<b>
<d>
<f />
</d>
<g />
</b>

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;

Is it possible to generate xml from SQL where I don't know the number of levels? [duplicate]

I wonder is there anyway to select hierarchy in SQL server 2005 and return xml format?
I have a database with a lot of data (about 2000 to 3000 records), and i am now using a function in SQL server 2005 to retrieve the data in hierarchy and return an XML but it seems not perfect because it's too slow when there is a lot of data
Here is my function
Database
ID Name Parent Order
Function
CREATE FUNCTION [dbo].[GetXMLTree]
(
#PARENT bigint
)
RETURNS XML
AS
BEGIN
RETURN /* value */
(SELECT [ID] AS "#ID",
[Name] AS "#Name",
[Parent] AS "#Parent",
[Order] AS "#Order",
dbo.GetXMLTree(Parent).query('/xml/item')
FROM MyDatabaseTable
WHERE [Parent]=#PARENT
ORDER BY [Order]
FOR XML PATH('item'),ROOT('xml'),TYPE)
END
I would like to use XML in hierarchy because with me there's alot of thing to do with it :)
Any best solutions plzzzzz
You can use a recursive CTE to build the hierarchy and loop over levels to build the XML.
-- Sample data
create table MyDatabaseTable(ID int, Name varchar(10), Parent int, [Order] int)
insert into MyDatabaseTable values
(1, 'N1', null, 1),
(2, 'N1_1', 1 , 1),
(3, 'N1_1_1', 2 , 1),
(4, 'N1_1_2', 2 , 2),
(5, 'N1_2', 1 , 2),
(6, 'N2', null, 1),
(7, 'N2_1', 6 , 1)
-- set #Root to whatever node should be root
declare #Root int = 1
-- Worktable that holds temp xml data and level
declare #Tree table(ID int, Parent int, [Order] int, [Level] int, XMLCol xml)
-- Recursive cte that builds #tree
;with Tree as
(
select
M.ID,
M.Parent,
M.[Order],
1 as [Level]
from MyDatabaseTable as M
where M.ID = #Root
union all
select
M.ID,
M.Parent,
M.[Order],
Tree.[Level]+1 as [Level]
from MyDatabaseTable as M
inner join Tree
on Tree.ID = M.Parent
)
insert into #Tree(ID, Parent, [Order], [Level])
select *
from Tree
declare #Level int
select #Level = max([Level]) from #Tree
-- Loop for each level
while #Level > 0
begin
update Tree set
XMLCol = (select
M.ID as '#ID',
M.Name as '#Name',
M.Parent as '#Parent',
M.[Order] as '#Order',
(select XMLCol as '*'
from #Tree as Tree2
where Tree2.Parent = M.ID
order by Tree2.[Order]
for xml path(''), type)
from MyDatabaseTable as M
where M.ID = Tree.ID
order by M.[Order]
for xml path('item'))
from #Tree as Tree
where Tree.[Level] = #Level
set #Level = #Level - 1
end
select XMLCol
from #Tree
where ID = #Root
Result
<item ID="1" Name="N1" Order="1">
<item ID="2" Name="N1_1" Parent="1" Order="1">
<item ID="3" Name="N1_1_1" Parent="2" Order="1" />
<item ID="4" Name="N1_1_2" Parent="2" Order="2" />
</item>
<item ID="5" Name="N1_2" Parent="1" Order="2" />
</item>
What benefit do you expect from using XML? I don't have a perfect solution for the case when you need XML by all means - but maybe you could also investigate alternatives??
With a recursive CTE (Common Table Expression), you could easily get your entire hierarchy in a single result set, and performance should be noticeably better than doing a recursive XML building function.
Check this CTE out:
;WITH Hierarchy AS
(
SELECT
ID, [Name], Parent, [Order], 1 AS 'Level'
FROM
dbo.YourDatabaseTable
WHERE
Parent IS NULL
UNION ALL
SELECT
t.ID, t.[Name], t.Parent, t.[Order], Level + 1 AS 'Level'
FROM
dbo.YourDatabaseTable t
INNER JOIN
Hierarchy h ON t.Parent = h.ID
)
SELECT *
FROM Hierarchy
ORDER BY [Level], [Order]
This gives you a single result set, where all rows are returned, ordered by level (1 for the root level, increasing 1 for each down level) and their [Order] column.
Could that be an alternative for you? Does it perform better??
I realise this answer is a bit late, but it might help some other unlucky person who is searching for answers to this problem. I have had similar performance problems using hierarchyid with XML:
It turned out for me that the simplest solution was actually just to call ToString() on the hierarchyid values before selecting as an XML column. In some cases this sped up my queries by a factor of ten!
Here's a snippet that exhibits the problem.
create table #X (id hierarchyid primary key, n int)
-- Generate 1000 random items
declare #n int = 1000
while #n > 0 begin
declare #parentID hierarchyID = null, #leftID hierarchyID = null, #rightID hierarchyID = null
select #parentID = id from #X order by newid()
if #parentID is not null select #leftID = id from #X where id.GetAncestor(1) = #parentID order by newid()
if #leftID is not null select #rightID = min(id) from #X where id.GetAncestor(1) = #parentID and id > #leftID
if #parentID is null set #parentID = '/'
declare #id hierarchyid = #parentID.GetDescendant(#leftID, #rightID)
insert #X (id, n) values (#id, #n)
set #n -= 1
end
-- select as XML using ToString() manually
select id.ToString() id, n from #X for xml path ('item'), root ('items')
-- select as XML without ToString() - about 10 times slower with SQL Server 2012
select id, n from #X for xml path ('item'), root ('items')
drop table #X

SQL Server select (top) two rows into two temp variables

I have a query which results in two or more rows (just one column) and I want to catch the first row value into first temp variable and second row value into second temp variable without using multiple times the select top 1 and select top 1 order by desc
Something like this;
Select row1 value into #tempvariable1, row2 value into #tempvariable2 from blah blah
You need somehow to identify the row (I am using a row ID in the example below, ordering by value - you can order by id or something else):
DECLARE #DataSource TABLE
(
[value] VARCHAR(12)
);
INSERT INTO #DataSource
VALUES ('value 1')
,('value 2')
,('value 3');
DECLARE #tempVariable1 VARCHAR(12)
,#tempVariable2 VARCHAR(12);
WITH DataSource ([value], [rowID]) AS
(
SELECT [value]
,ROW_NUMBER() OVER (ORDER BY [value])
FROM #DataSource
)
SELECT #tempVariable1 = IIF([rowID] = 1, [value], #tempVariable1)
,#tempVariable2 = IIF([rowID] = 2, [value], #tempVariable2)
FROM DataSource;
SELECT #tempVariable1
,#tempVariable2;
You can use a CTE where you will get the X values you need and then select from it:
declare #data table(id int);
insert into #data(id) values(8), (6), (4), (3);
with vals(id, n) as (
Select top(2) id, ROW_NUMBER() over(order by id)
From #data
)
Select #A = (Select id From vals Where n = 1)
, #B = (Select id From vals Where n = 2)
You could also use PIVOT:
Select #A = [1], #B = [2]
From (
Select id, ROW_NUMBER() over(order by id)
From #data
) v(id, n)
PIVOT (
max(id) FOR n in ([1], [2])
) as piv
You have two options
Let's say we test case is build as below
create table dbo.Test
(
value varchar(100) not null
)
GO
insert into dbo.Test
values
('A'),('B'),('NO THIS ONE'),('NO THIS ONE'),('NO THIS ONE')
GO
Now let's say you fetch your data as below
select t.value
from dbo.Test t
where t.value != 'NO THIS ONE'
GO
The first and easier option is to save the data in a temp table
declare #results as Table (value varchar(100))
insert into #results
select t.value
from dbo.Test t
where t.value != 'NO THIS ONE'
you still use TOP 1 BUT not in the entire data, only in the results.
Use TOP 1 to find the first result and a second TOP 1 where value is different from the first.
declare #A varchar(100), #B varchar(100)
set #A = (select top 1 r.value from #results r)
set #B = (select top 1 r.value from #results r where r.value != #A)
select #A, #B
GO
This approach have the advantage of performance.
Of course that don't work great if both values are equal. You can fix it by using a top 1 and ordering in the inverse order.
There's a better alternative using rownumber.
It works because if you set a variable when returning multiple rows the varible sticks with the last one (in fact it's reseted for each row iteration).
The case statement makes sure the variable #A is seted only on the first row iteration.
declare #A varchar(100), #B varchar(100)
/* This way #B receives the last value and #A the first */
select #B = t.value,
#A = (case when ROW_NUMBER() OVER(order by t.Value) = 1
then t.Value else #A
end)
from dbo.Test t
where t.value != 'NO THIS ONE'
select #A, #B

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 Server group by count eliminate duplicates [duplicate]

How do I get:
id Name Value
1 A 4
1 B 8
2 C 9
to
id Column
1 A:4, B:8
2 C:9
No CURSOR, WHILE loop, or User-Defined Function needed.
Just need to be creative with FOR XML and PATH.
[Note: This solution only works on SQL 2005 and later. Original question didn't specify the version in use.]
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
If it is SQL Server 2017 or SQL Server Vnext, SQL Azure you can use STRING_AGG as below:
SELECT id, STRING_AGG(CONCAT(name, ':', [value]), ', ')
FROM #YourTable
GROUP BY id
using XML path will not perfectly concatenate as you might expect... it will replace "&" with "&" and will also mess with <" and ">
...maybe a few other things, not sure...but you can try this
I came across a workaround for this... you need to replace:
FOR XML PATH('')
)
with:
FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')
...or NVARCHAR(MAX) if thats what youre using.
why the hell doesn't SQL have a concatenate aggregate function? this is a PITA.
I ran into a couple of problems when I tried converting Kevin Fairchild's suggestion to work with strings containing spaces and special XML characters (&, <, >) which were encoded.
The final version of my code (which doesn't answer the original question but may be useful to someone) looks like this:
CREATE TABLE #YourTable ([ID] INT, [Name] VARCHAR(MAX), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'Oranges & Lemons',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'1 < 2',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT [ID],
STUFF((
SELECT ', ' + CAST([Name] AS VARCHAR(MAX))
FROM #YourTable WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE
/* Use .value to uncomment XML entities e.g. > < etc*/
).value('.','VARCHAR(MAX)')
,1,2,'') as NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Rather than using a space as a delimiter and replacing all the spaces with commas, it just pre-pends a comma and space to each value then uses STUFF to remove the first two characters.
The XML encoding is taken care of automatically by using the TYPE directive.
Another option using Sql Server 2005 and above
---- test data
declare #t table (OUTPUTID int, SCHME varchar(10), DESCR varchar(10))
insert #t select 1125439 ,'CKT','Approved'
insert #t select 1125439 ,'RENO','Approved'
insert #t select 1134691 ,'CKT','Approved'
insert #t select 1134691 ,'RENO','Approved'
insert #t select 1134691 ,'pn','Approved'
---- actual query
;with cte(outputid,combined,rn)
as
(
select outputid, SCHME + ' ('+DESCR+')', rn=ROW_NUMBER() over (PARTITION by outputid order by schme, descr)
from #t
)
,cte2(outputid,finalstatus,rn)
as
(
select OUTPUTID, convert(varchar(max),combined), 1 from cte where rn=1
union all
select cte2.outputid, convert(varchar(max),cte2.finalstatus+', '+cte.combined), cte2.rn+1
from cte2
inner join cte on cte.OUTPUTID = cte2.outputid and cte.rn=cte2.rn+1
)
select outputid, MAX(finalstatus) from cte2 group by outputid
Install the SQLCLR Aggregates from http://groupconcat.codeplex.com
Then you can write code like this to get the result you asked for:
CREATE TABLE foo
(
id INT,
name CHAR(1),
Value CHAR(1)
);
INSERT INTO dbo.foo
(id, name, Value)
VALUES (1, 'A', '4'),
(1, 'B', '8'),
(2, 'C', '9');
SELECT id,
dbo.GROUP_CONCAT(name + ':' + Value) AS [Column]
FROM dbo.foo
GROUP BY id;
Eight years later... Microsoft SQL Server vNext Database Engine has finally enhanced Transact-SQL to directly support grouped string concatenation. The Community Technical Preview version 1.0 added the STRING_AGG function and CTP 1.1 added the WITHIN GROUP clause for the STRING_AGG function.
Reference: https://msdn.microsoft.com/en-us/library/mt775028.aspx
SQL Server 2005 and later allow you to create your own custom aggregate functions, including for things like concatenation- see the sample at the bottom of the linked article.
This is just an addition to Kevin Fairchild's post (very clever by the way). I would have added it as a comment, but I don't have enough points yet :)
I was using this idea for a view I was working on, however the items I was concatinating contained spaces. So I modified the code slightly to not use spaces as delimiters.
Again thanks for the cool workaround Kevin!
CREATE TABLE #YourTable ( [ID] INT, [Name] CHAR(1), [Value] INT )
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'A', 4)
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'B', 8)
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (2, 'C', 9)
SELECT [ID],
REPLACE(REPLACE(REPLACE(
(SELECT [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) as A
FROM #YourTable
WHERE ( ID = Results.ID )
FOR XML PATH (''))
, '</A><A>', ', ')
,'<A>','')
,'</A>','') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
An example would be
In Oracle you can use LISTAGG aggregate function.
Original records
name type
------------
name1 type1
name2 type2
name2 type3
Sql
SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name
Result in
name type
------------
name1 type1
name2 type2; type3
This kind of question is asked here very often, and the solution is going to depend a lot on the underlying requirements:
https://stackoverflow.com/search?q=sql+pivot
and
https://stackoverflow.com/search?q=sql+concatenate
Typically, there is no SQL-only way to do this without either dynamic sql, a user-defined function, or a cursor.
Just to add to what Cade said, this is usually a front-end display thing and should therefore be handled there. I know that sometimes it's easier to write something 100% in SQL for things like file export or other "SQL only" solutions, but most of the times this concatenation should be handled in your display layer.
Don't need a cursor... a while loop is sufficient.
------------------------------
-- Setup
------------------------------
DECLARE #Source TABLE
(
id int,
Name varchar(30),
Value int
)
DECLARE #Target TABLE
(
id int,
Result varchar(max)
)
INSERT INTO #Source(id, Name, Value) SELECT 1, 'A', 4
INSERT INTO #Source(id, Name, Value) SELECT 1, 'B', 8
INSERT INTO #Source(id, Name, Value) SELECT 2, 'C', 9
------------------------------
-- Technique
------------------------------
INSERT INTO #Target (id)
SELECT id
FROM #Source
GROUP BY id
DECLARE #id int, #Result varchar(max)
SET #id = (SELECT MIN(id) FROM #Target)
WHILE #id is not null
BEGIN
SET #Result = null
SELECT #Result =
CASE
WHEN #Result is null
THEN ''
ELSE #Result + ', '
END + s.Name + ':' + convert(varchar(30),s.Value)
FROM #Source s
WHERE id = #id
UPDATE #Target
SET Result = #Result
WHERE id = #id
SET #id = (SELECT MIN(id) FROM #Target WHERE #id < id)
END
SELECT *
FROM #Target
Let's get very simple:
SELECT stuff(
(
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
FOR XML PATH('')
)
, 1, 2, '')
Replace this line:
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
With your query.
You can improve performance significant the following way if group by contains mostly one item:
SELECT
[ID],
CASE WHEN MAX( [Name]) = MIN( [Name]) THEN
MAX( [Name]) NameValues
ELSE
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
END
FROM #YourTable Results
GROUP BY ID
didn't see any cross apply answers, also no need for xml extraction. Here is a slightly different version of what Kevin Fairchild wrote. It's faster and easier to use in more complex queries:
select T.ID
,MAX(X.cl) NameValues
from #YourTable T
CROSS APPLY
(select STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = T.ID)
FOR XML PATH(''))
,1,2,'') [cl]) X
GROUP BY T.ID
Using the Stuff and for xml path operator to concatenate rows to string :Group By two columns -->
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',5)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
-- retrieve each unique id and name columns and concatonate the values into one column
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES EACH APPLICATION : VALUE SET
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
SELECT
[ID],[Name] , --these are acting as the group by clause
STUFF((
SELECT ', '+ CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES THE VALUES FOR EACH ID NAME COMBINATION
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID, name
DROP TABLE #YourTable
Using Replace Function and FOR JSON PATH
SELECT T3.DEPT, REPLACE(REPLACE(T3.ENAME,'{"ENAME":"',''),'"}','') AS ENAME_LIST
FROM (
SELECT DEPT, (SELECT ENAME AS [ENAME]
FROM EMPLOYEE T2
WHERE T2.DEPT=T1.DEPT
FOR JSON PATH,WITHOUT_ARRAY_WRAPPER) ENAME
FROM EMPLOYEE T1
GROUP BY DEPT) T3
For sample data and more ways click here
If you have clr enabled you could use the Group_Concat library from GitHub
Another example without the garbage: ",TYPE).value('(./text())[1]','VARCHAR(MAX)')"
WITH t AS (
SELECT 1 n, 1 g, 1 v
UNION ALL
SELECT 2 n, 1 g, 2 v
UNION ALL
SELECT 3 n, 2 g, 3 v
)
SELECT g
, STUFF (
(
SELECT ', ' + CAST(v AS VARCHAR(MAX))
FROM t sub_t
WHERE sub_t.g = main_t.g
FOR XML PATH('')
)
, 1, 2, ''
) cg
FROM t main_t
GROUP BY g
Input-output is
************************* -> *********************
* n * g * v * * g * cg *
* - * - * - * * - * - *
* 1 * 1 * 1 * * 1 * 1, 2 *
* 2 * 1 * 2 * * 2 * 3 *
* 3 * 2 * 3 * *********************
*************************
I used this approach which may be easier to grasp. Get a root element, then concat to choices any item with the same ID but not the 'official' name
Declare #IdxList as Table(id int, choices varchar(max),AisName varchar(255))
Insert into #IdxLIst(id,choices,AisName)
Select IdxId,''''+Max(Title)+'''',Max(Title) From [dbo].[dta_Alias]
where IdxId is not null group by IdxId
Update #IdxLIst
set choices=choices +','''+Title+''''
From #IdxLIst JOIN [dta_Alias] ON id=IdxId And Title <> AisName
where IdxId is not null
Select * from #IdxList where choices like '%,%'
For all my healthcare folks out there:
SELECT
s.NOTE_ID
,STUFF ((
SELECT
[note_text] + ' '
FROM
HNO_NOTE_TEXT s1
WHERE
(s1.NOTE_ID = s.NOTE_ID)
ORDER BY [line] ASC
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,
1,
2,
'') AS NOTE_TEXT_CONCATINATED
FROM
HNO_NOTE_TEXT s
GROUP BY NOTE_ID

Resources