Show Grouped By Items in Comma Separated Format - sql

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

Related

Using GROUP BY with FOR XML PATH in SQL Server 2016

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 |
+------+-----------------+

Unpivot multiple columns in Snowflake

I have a table that looks as follows:
I need to unpivot the Rating and the Comments as follows:
What is the best way to do this in Snowflake?
Note: there are some cells in the comment columns that are NULL
Adding details:
create or replace table reviews(name varchar(50), acting_rating int, acting_comments text, comedy_rating int, comedy_comments text);
insert into reviews values
('abc', 4, NULL, 1, 'NO'),
('xyz', 3, 'some', 1, 'haha'),
('lmn', 1, 'what', 4, NULL);
select * from reviews;
select name, skill, skill_rating, comments
from reviews
unpivot(skill_rating for skill in (acting_rating, comedy_rating))
unpivot(comments for skill_comments in (acting_comments,comedy_comments))
--Following where clause is added to filter the irrelevant comments due to multiple unpivots
where substr(skill,1,position('_',skill)-1) = substr(skill_comments,1,position('_',skill_comments)-1)
order by name;
will produce produce the desired results, but with data that has NULLs, the unpivoted rows that have NULLs go missing from the output:
NAME SKILL SKILL_RATING COMMENTS
abc COMEDY_RATING 1 NO
lmn ACTING_RATING 1 what
xyz ACTING_RATING 3 some
xyz COMEDY_RATING 1 haha
If all you need to solve is for the table specified in the question - you can do it manually with a set of UNION ALL:
select NAME
, 'ACTING_RATING' as SKILL, ACTING_RATING as SKILL_RATING, ACTING_COMMENTS as SKILL_COMMENTS
from DATA
union all
select NAME
, 'COMEDY_RATING', COMEDY_RATING, COMEDY_COMMENTS
from DATA
union all
select NAME
, 'MUSICAL_PERFORMANCE_RATING', MUSICAL_PERFORMANCE_RATING, MUSICAL_PERFORMANCE_COMMENTS
from DATA
This is a basic script and should give the desired output
create or replace table reviews(name varchar(50), acting_rating int, acting_comments text, comedy_rating int, comedy_comments text);
insert into reviews values
('abc', 4, 'something', 1, 'NO'),
('xyz', 3, 'some', 1, 'haha'),
('lmn', 1, 'what', 4, 'hahaha');
select * from reviews;
select name, skill, skill_rating, comments
from reviews
unpivot(skill_rating for skill in (acting_rating, comedy_rating))
unpivot(comments for skill_comments in (acting_comments,comedy_comments))
--Following where clause is added to filter the irrelevant comments due to multiple unpivots
where substr(skill,1,position('_',skill)-1) = substr(skill_comments,1,position('_',skill_comments)-1)
order by name;
If the goal is to store the unpivoted result as a table then INSERT ALL could be used to unpivot mutliple columns at once:
Setup:
create or replace table reviews(
name varchar(50), acting_rating int,
acting_comments text, comedy_rating int, comedy_comments text);
insert into reviews values
('abc', 4, NULL, 1, 'NO'),
('xyz', 3, 'some', 1, 'haha'),
('lmn', 1, 'what', 4, NULL);
select * from reviews;
Query:
CREATE OR REPLACE TABLE reviews_transposed(
name VARCHAR(50)
,skill TEXT
,skill_rating INT
,skill_comments TEXT
);
INSERT ALL
INTO reviews_transposed(name, skill, skill_rating, skill_comments)
VALUES (name, 'ACTING_RATING', acting_rating, acting_comments)
INTO reviews_transposed(name, skill, skill_rating, skill_comments)
VALUES (name, 'COMEDY_RATING', comedy_rating, comedy_comments)
SELECT *
FROM reviews;
SELECT *
FROM reviews_transposed;
Before:
After:
This approach has one significant advantage over UNION ALL approach proposed by Felippe, when saving into table (the number of table scans and thus partition read is growing for each UNION ALL wheareas INSERT ALL scans source table only once.
INSERT INTO reviews_transposed
select NAME
, 'ACTING_RATING' as SKILL, ACTING_RATING as SKILL_RATING, ACTING_COMMENTS as SKILL_COMMENTS
from reviews
union all
select NAME
, 'COMEDY_RATING', COMEDY_RATING, COMEDY_COMMENTS
from reviews;
vs INSERT ALL
Back in TSQL days i'd just use a CROSS APPLY. The nearest equivalent in snowflake would be something like:
create or replace TEMPORARY table reviews(name varchar(50), acting_rating int, acting_comments text, comedy_rating int, comedy_comments text);
insert into reviews values
('abc', 4, NULL, 1, 'NO'),
('xyz', 3, 'some', 1, 'haha'),
('lmn', 1, 'what', 4, NULL);
SELECT R.NAME
,P.VALUE:SKILL::VARCHAR(100) AS SKILL
,P.VALUE:RATING::NUMBER AS RATING
,P.VALUE:COMMENTS::VARCHAR(1000) AS COMMENTS
FROM reviews R
,TABLE(FLATTEN(INPUT => ARRAY_CONSTRUCT(
OBJECT_CONSTRUCT('SKILL','COMEDY','RATING',R.COMEDY_RATING,'COMMENTS',R.COMEDY_COMMENTS),
OBJECT_CONSTRUCT('SKILL','ACTING','RATING',R.ACTING_RATING,'COMMENTS',R.ACTING_COMMENTS)
)
)) AS P;
This only hits the source table once and preserves NULLs.
ResultSet
I've had same problem,
Here is my solution for unpivoting by two categories AND keeping nulls:
First you replace NULL's with some string, for example: 'NULL'
Then brake the two unpivots into two separate cte's and create common category column to join them again later, 'skill' in your case.
Lastly, join the two cte's by name and skill category, replace the 'NULL' string with actual NULL
create or replace table reviews(name varchar(50), acting_rating int, acting_comments text, comedy_rating int, comedy_comments text);
insert into reviews values
('abc', 4, 'something', 1, 'NO'),
('xyz', 3, 'some', 1, 'haha'),
('lmn', 1, 'what', 4, 'hahaha');
WITH base AS (SELECT name
, acting_rating
, IFNULL(acting_comments, 'NULL') AS acting_comments
, comedy_rating
, IFNULL(comedy_comments, 'NULL') AS comedy_comments
FROM reviews
)
, skill_rating AS (SELECT name
, REPLACE(skill, '_RATING', '') AS skill
, skill_rating
FROM base
UNPIVOT (skill_rating FOR skill IN (acting_rating, comedy_rating))
)
, comments AS (SELECT name
, REPLACE(skill_comments, '_COMMENTS', '') AS skill
, comments
FROM base
UNPIVOT (comments FOR skill_comments IN (acting_comments,comedy_comments))
)
SELECT s.name
, s.skill
, s.skill_rating
, NULLIF(c.comments, 'NULL') AS comments
FROM skill_rating AS s
JOIN comments AS c
ON s.name = c.name
AND s.skill = c.skill
ORDER BY name;
The result:
name skill skill_rating comments
abc ACTING 4 <null>
abc COMEDY 1 NO
lmn ACTING 1 what
lmn COMEDY 4 <null>
xyz ACTING 3 some
xyz COMEDY 1 haha

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

How to get the corresponding comma-separated text for a string of comma separated codes?

I've a table, there is a column called "locations" which is in type of varchar(max). It carries string of comma-separated code, e.g. '1, 3, 4'. On the other hand, I've a table which map these code to some locations, e.g.
1 -- British
2 -- New Zealand
3 -- Hong Kong
4 -- Taiwan
My problem is, I'm making a VIEW which will map and replace the content of the column "locations" to the corresponding comma-separated text, e.g. '1, 3, 4' will be 'British, Hong Kong, Taiwan'
This is really urgent for the company project, please kindly advise.
Thank you!
Regards,
William
There might be simpler solutions to do this but here is one way.
Table structure
create table Locations(LocationID int, Location varchar(50))
create table OtherTable(ID int, Locations varchar(max))
Test data
insert into Locations values(1, 'Location <1>')
insert into Locations values(2, 'Location <2>')
insert into Locations values(3, 'Location <3>')
insert into Locations values(4, 'Location <4>')
insert into Locations values(5, 'Location <5>')
insert into OtherTable values (1, '')
insert into OtherTable values (2, '2')
insert into OtherTable values (3, '1, 3 ,5')
Query
;with cte as
(
select
T.ID,
coalesce(L.Location, '') as Location
from OtherTable as T
cross apply
(select cast('<r>'+replace(T.Locations, ',', '</r><r>')+'</r>' as xml)) LocXML(XMLCol)
cross apply
LocXML.XMLCol.nodes('r') LocID(IDCol)
left outer join Locations as L
on L.LocationID = LocID.IDCol.value('.', 'int')
)
select
C1.ID,
stuff((select ', '+C2.Location
from cte as C2
where C1.ID = C2.ID
for xml path(''), type).value('text()[1]', 'nvarchar(max)'), 1, 2, '') as Locations
from cte as C1
group by C1.ID
Result
ID Locations
--- ----------------------------------------
1
2 Location <2>
3 Location <1>, Location <3>, Location <5>

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';