Using GROUP BY with FOR XML PATH in SQL Server 2016 - sql

I am trying to
group by ID and
aggregate multiple comments into a single row
Right now, I can do the no. 2 part for a single ID (ID = 1006), but I would like to aggregate comments for all IDs. I am struggling where and how to add "group by" clause in my query.
Here is the query:
create table Comments (ID int, Comment nvarchar(150), RegionCode int)
insert into Comments values (1006, 'I', 1)
, (1006, 'am', 1)
, (1006, 'good', 1)
, (1006, 'bad', 2)
, (2, 'You', 1)
, (2, 'are', 1)
, (2, 'awesome', 1)
SELECT
SUBSTRING((SELECT Comment
FROM Comments
WHERE ID = 1006 AND RegionCode != 2
FOR XML PATH('')), 1, 999999) AS Comment_Agg
My desired result looks something like this:
FYI, I am using FOR XML PATH here to aggregate multiple comments into a single row because STRING_AGG function is not supported in my version - SQL Server 2016 (v13.x).

Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID int, Comment nvarchar(150));
INSERT INTO #tbl VALUES
(1006, 'I'),
(1006, 'am'),
(1006, 'good'),
(2, 'You'),
(2, 'are'),
(2, 'awesome');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = SPACE(1);
SELECT p.ID
, STUFF((SELECT #separator + Comment
FROM #tbl AS c
WHERE c.ID = p.ID
FOR XML PATH('')), 1, LEN(#separator), '') AS Result
FROM #tbl AS p
GROUP BY p.ID
ORDER BY p.ID;
Output
+------+-----------------+
| ID | Result |
+------+-----------------+
| 2 | You are awesome |
| 1006 | I am good |
+------+-----------------+

Related

I need sql query for order by values of given parameters?

I need a query for order by given parameters
My table like this
ID Name Type
1 Argentine Standard
2 Spain Critical
3 France Critical
4 Germany Standard
5 Brazil Standard
6 Italy Standard
I am sending the parameter as Germany,Spain,Brazil,Argentine my output should be
ID Name Type
4 Germany Standard
2 Spain Critical
5 Brazil Standard
1 Argentine Standard
At present i used in query and i got the output in order by id that means it shows the result as in database order but i need to order by query parameter order?
can anyone help me for query?
according to your output I can suggest you this query. You can also alter order by clause as per your requirement:
select id,name,type from stack where name in('Germany','Spain','Brazil','Argentine') order by type,name,id desc
may be you are sending Comma Separated Input then we can convert them into rows and join with the table Data to get Required Output.
declare #t varchar(50) = ' Germany,Spain,Brazil,Argentine'
Declare #tt table (ID INT IDENTITY(1,1),val varchar(10))
insert into #tt (val)
SELECT
LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Certs
FROM
(
SELECT CAST('<XMLRoot><RowData>' + REPLACE(#t,',','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n)
DECLARE #Table1 TABLE
(ID int, Name varchar(9), Type varchar(8))
;
INSERT INTO #Table1
(ID, Name, Type)
VALUES
(1, 'Argentine', 'Standard'),
(2, 'Spain', 'Critical'),
(3, 'France', 'Critical'),
(4, 'Germany', 'Standard'),
(5, 'Brazil', 'Standard'),
(6, 'Italy', 'Standard')
;
select T.ID,TT.val,T.Type from #Table1 T
INNER JOIN #tt TT
ON T.Name = TT.val
ORDER BY TT.ID

Show Grouped By Items in Comma Separated Format

I have a table like below:
create table Location
(
ContinentID int not null,
CountryID int not null,
StateCode nvarchar(10) not null
)
Insert into Location Values (1, 1, 'AP')
Insert into Location Values (1, 1, 'WB')
Insert into Location Values (1, 1, 'MH')
Insert into Location Values (1, 2, 'KA')
Insert into Location Values (1, 2, 'ID')
Insert into Location Values (3, 1, 'NY')
Insert into Location Values (3, 1, 'WA')
Insert into Location Values (3, 2, 'VI')
Here I need all the state codes should be shown in a comma separated format based on ContinentID and CountryID. So the output must look like below:
ContinentID CountryID StateCodes
----------- --------- ----------
1 1 AP,WB,MH
1 2 KA,ID
3 1 NY,WA
3 2 VI
I don't have much idea about SQL queries, I tried one below, but it didn't work:
SELECT Continentid, CountryID, CONCAT(StateCode, ',') FROM Location
GROUP BY Continentid, CountryID
How can I get the desired output using a single SQL Query ? Any help is appreciated.
In T-SQL, FOR XML PATH probably gives you the best performance. STUFF handles the out of place leading comma.
Click here to see the SQL Fiddle.
SELECT ContinentID, CountryID,
StateCode =
STUFF((SELECT ', ' + StateCode
FROM Location b
WHERE b.ContinentID = a.ContinentID
and
b.CountryID = a.CountryID
FOR XML PATH('')), 1, 2, '')
FROM Location a
GROUP BY ContinentID, CountryID
This answer can also help.
Ah -- it's tricky :) Here is one of the methods from here adapted for your data structure. I have verified that it produces the result you want except for a trailing comma...
https://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/
SELECT p1.ContinentID, p1.CountryID,
( SELECT StateCode + ','
FROM Location p2
WHERE p2.ContinentID = p1.ContinentID AND p2.CountryID = p1.CountryID
ORDER BY StateCode
FOR XML PATH('') ) AS StateCodes
FROM Location p1
GROUP BY ContinentID, CountryID

SQL Server 2000 equivalent of GROUP_CONCAT function

I tried to use the GROUP_CONCAT function in SQL Server 2000 but it returns an error:
'group_concat' is not a recognized function name"
So I guess there is an other function for group_concat in SQL Server 2000? Can you tell me what it is?
Unfortunately since you are using SQL Server 2000 you cannot use FOR XML PATH to concatenate the values together.
Let's say we have the following sample Data:
CREATE TABLE yourtable ([id] int, [name] varchar(4));
INSERT INTO yourtable ([id], [name])
VALUES (1, 'John'), (1, 'Jim'),
(2, 'Bob'), (3, 'Jane'), (3, 'Bill'), (4, 'Test'), (4, '');
One way you could generate the list together would be to create a function. A sample function would be:
CREATE FUNCTION dbo.List
(
#id int
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #r VARCHAR(8000)
SELECT #r = ISNULL(#r+', ', '') + name
FROM dbo.yourtable
WHERE id = #id
and Name > '' -- add filter if you think you will have empty strings
RETURN #r
END
Then when you query the data, you will pass a value into the function to concatenate the data into a single row:
select distinct id, dbo.list(id) Names
from yourtable;
See SQL Fiddle with Demo. This gives you a result:
| ID | NAMES |
-------------------
| 1 | John, Jim |
| 2 | Bob |
| 3 | Jane, Bill |
| 4 | Test |

How do I join an unknown number of rows to another row?

I have this scenario:
Table A:
---------------
ID| SOME_VALUE|
---------------
1 | 123223 |
2 | 1232ff |
---------------
Table B:
------------------
ID | KEY | VALUE |
------------------
23 | 1 | 435 |
24 | 1 | 436 |
------------------
KEY is a reference to to Table A's ID. Can I somehow join these tables so that I get the following result:
Table C
-------------------------
ID| SOME_VALUE| | |
-------------------------
1 | 123223 |435 |436 |
2 | 1232ff | | |
-------------------------
Table C should be able to have any given number of columns depending on how many matching values that are found in Table B.
I hope this enough to explain what I'm after here.
Thanks.
You need to use a Dynamic PIVOT clause in order to do this.
EDIT:
Ok so I've done some playing around and based on the following sample data:
Create Table TableA
(
IDCol int,
SomeValue varchar(50)
)
Create Table TableB
(
IDCol int,
KEYCol int,
Value varchar(50)
)
Insert into TableA
Values (1, '123223')
Insert Into TableA
Values (2,'1232ff')
Insert into TableA
Values (3, '222222')
Insert Into TableB
Values( 23, 1, 435)
Insert Into TableB
Values( 24, 1, 436)
Insert Into TableB
Values( 25, 3, 45)
Insert Into TableB
Values( 26, 3, 46)
Insert Into TableB
Values( 27, 3, 435)
Insert Into TableB
Values( 28, 3, 437)
You can execute the following Dynamic SQL.
declare #sql varchar(max)
declare #pivot_list varchar(max)
declare #pivot_select varchar(max)
Select
#pivot_list = Coalesce(#Pivot_List + ', ','') + '[' + Value +']',
#Pivot_select = Coalesce(#pivot_Select, ', ','') +'IsNull([' + Value +'],'''') as [' + Value + '],'
From
(
Select distinct Value From dbo.TableB
)PivotCodes
Set #Sql = '
;With p as (
Select a.IdCol,
a.SomeValue,
b.Value
From dbo.TableA a
Left Join dbo.TableB b on a.IdCol = b.KeyCol
)
Select IdCol, SomeValue ' + Left(#pivot_select, Len(#Pivot_Select)-1) + '
From p
Pivot ( Max(Value) for Value in (' + #pivot_list + '
)
)as pvt
'
exec (#sql)
This gives you the following output:
Although this works at the moment it would be a nightmare to maintain. I'd recommend trying to achieve these results somewhere else. i.e not in SQL!
Good luck!
As Barry has amply illustrated, it's possible to get multiple columns using a dynamic pivot.
I've got a solution that might get you what you need, except that it puts all of the values into a single VARCHAR column. If you can split those results, then you can get what you need.
This method is a trick in SQL Server 2005 that you can use to form a string out of a column of values.
CREATE TABLE #TableA (
ID INT,
SomeValue VARCHAR(50)
);
CREATE TABLE #TableB (
ID INT,
TableAKEY INT,
BValue VARCHAR(50)
);
INSERT INTO #TableA VALUES (1, '123223');
INSERT INTO #TableA VALUES (2, '1232ff');
INSERT INTO #TableA VALUES (3, '222222');
INSERT INTO #TableB VALUES (23, 1, 435);
INSERT INTO #TableB VALUES (24, 1, 436);
INSERT INTO #TableB VALUES (25, 3, 45);
INSERT INTO #TableB VALUES (26, 3, 46);
INSERT INTO #TableB VALUES (27, 3, 435);
INSERT INTO #TableB VALUES (28, 3, 437);
SELECT
a.ID
,a.SomeValue
,RTRIM(bvals.BValues) AS ValueList
FROM #TableA AS a
OUTER APPLY (
-- This has the effect of concatenating all of
-- the BValues for the given value of a.ID.
SELECT b.BValue + ' ' AS [text()]
FROM #TableB AS b
WHERE a.ID = b.TableAKEY
ORDER BY b.ID
FOR XML PATH('')
) AS bvals (BValues)
ORDER BY a.ID
;
You'll get this as a result:
ID SomeValue ValueList
--- ---------- --------------
1 123223 435 436
2 1232ff NULL
3 222222 45 46 435 437
This looks like something a database shouldn't do. Firstly; a table cannot have arbitrary number of columns depending on whatever you'll store. So you will have to put up a maximum number of values anyway. You can get around this by using comma seperated values as value for that cell (or a similar pivot-like solution).
However; if you do have table A and B; i recommend keeping to those two tables; as they seem to be pretty normalised. Should you need a list of b.value given an input a.some_value, the following sql query gives that list.
select b.value from a,b where b.key=a.id a.some_value='INPUT_VALUE';

Sorting tree with other column in SQL Server 2008

I have a table which implements a tree using hierarchyid column
Sample data:
People \
Girls \1\
Zoey \1\1\
Kate \1\2\
Monica \1\3\
Boys \2\
Mark \2\1\
David \2\2\
This is the order using hierarchyid column as sort column
I would like to sort data using hierarchyid but also using name so it would look like this:
People \
Boys \2\
David \2\2\
Mark \2\1\
Girls \1\
Kate \1\2\
Monica \1\3\
Zoey \1\1\
Is there a simple solution to do this?
Can it be done with a single SQL query?
Rewrite your query as a recursive CTE:
DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, name NVARCHAR(4000) NOT NULL, path HIERARCHYID)
INSERT
INTO #table
VALUES
(1, 'People', '/'),
(2, 'Girls', '/1/'),
(3, 'Boys', '/2/'),
(4, 'Zoey', '/1/1/'),
(5, 'Kate', '/1/2/'),
(6, 'Monica', '/1/3/'),
(7, 'Mark', '/2/1/'),
(8, 'David', '/2/2/')
;WITH q AS
(
SELECT *, HIERARCHYID::Parse('/') AS newpath
FROM #table
WHERE path = HIERARCHYID::GetRoot()
UNION ALL
SELECT t.*, HIERARCHYID::Parse(q.newpath.ToString() + CAST(ROW_NUMBER() OVER (ORDER BY t.name) AS NVARCHAR(MAX)) + '/')
FROM q
JOIN #table t
ON t.path.IsDescendantOf(q.path) = 1
AND t.path.GetLevel() = q.path.GetLevel() + 1
)
SELECT *
FROM q
ORDER BY
newpath