Unpivot multiple columns in Snowflake - sql

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

Related

T-SQL: convert columns to rows and insert/update another table

Looking for non fancy, easily debugable for junior developer solution...
In SQL Server 2008 R2, I have to update data from #data table to #tests table in desired format. I am not sure how I would archive result using T-SQL query?
NOTE: temp tables have only 3 columns for sample purpose only but real table have more than 50 columns for each set.
Here is what my tables look like:
IF OBJECT_ID('tempdb..#tests') IS NOT NULL
DROP TABLE #tests
GO
CREATE TABLE #tests
(
id int,
FirstName varchar(100),
LastName varchar(100),
UniueNumber varchar(100)
)
IF OBJECT_ID('tempdb..#data') IS NOT NULL
DROP TABLE #data
GO
CREATE TABLE #data
(
id int,
FirstName1 varchar(100),
LastName1 varchar(100),
UniueNumber1 varchar(100),
FirstName2 varchar(100),
LastName2 varchar(100),
UniueNumber2 varchar(100),
FirstName3 varchar(100),
LastName3 varchar(100),
UniueNumber3 varchar(100),
FirstName4 varchar(100),
LastName4 varchar(100),
UniueNumber4 varchar(100),
FirstName5 varchar(100),
LastName5 varchar(100),
UniueNumber5 varchar(100),
FirstName6 varchar(100),
LastName6 varchar(100),
UniueNumber6 varchar(100),
FirstName7 varchar(100),
LastName7 varchar(100),
UniueNumber7 varchar(100)
)
INSERT INTO #data
VALUES (111, 'Tom', 'M', '12345', 'Sam', 'M', '65432', 'Chris', 'PATT', '54656', 'Sean', 'Meyer', '865554', 'Mike', 'Max', '999999', 'Tee', 'itc', '656546444', 'Mickey', 'Mul', '65443231')
INSERT INTO #data
VALUES (222, 'Kurr', 'P', '22222', 'Yammy', 'G', '33333', 'Saras', 'pi', '55555', 'Man', 'Shey', '666666', 'Max', 'Dopit', '66666678', '', '', '', '', '', '')
INSERT INTO #data
VALUES (333, 'Mia', 'K', '625344', 'Tee', 'TE', '777766', 'david', 'mot', '4444444', 'Jeff', 'August', '5666666', 'Mylee', 'Max', '0000000', '', '', '', 'Amy', 'Marr', '55543444')
SELECT *
FROM #data
I want to insert/update data into #tests table from #data table.
Insert data into #tests table if id and UniqueNumber combination does not exists from #data table. If combination exists then update data into #tests table from #data table
This is desired output into #tests table
Here is an option that will dynamically UNPIVOT your data without using Dynamic SQL
To be clear: UNPIVOT would be more performant, but you don't have to enumerate the 50 columns.
This is assuming your columns end with a NUMERIC i.e. FirstName##
Example
Select ID
,FirstName
,LastName
,UniueNumber -- You could use SSN = UniueNumber
From (
SELECT A.ID
,Grp
,Col = replace([Key],Grp,'')
,Value
FROM #data A
Cross Apply (
Select [Key]
,Value
,Grp = substring([Key],patindex('%[0-9]%',[Key]),25)
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
) B
) src
Pivot ( max(Value) for Col in ([FirstName],[LastName],[UniueNumber]) ) pvt
Order By ID,Grp
Results
UPDATE XML Version
Select ID
,FirstName
,LastName
,UniueNumber
From (
SELECT A.ID
,Grp = substring(Item,patindex('%[0-9]%',Item),50)
,Col = replace(Item,substring(Item,patindex('%[0-9]%',Item),50),'')
,Value
FROM #data A
Cross Apply ( values (convert(xml,(Select A.* for XML RAW)))) B(XData)
Cross Apply (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From B.XData.nodes('//#*') xNode(xAttr)
) C
Where Item not in ('ID')
) src
Pivot ( max(Value) for Col in (FirstName,LastName,UniueNumber) ) pvt
Order By ID,Grp
One way is to query each group of columns separately and UNION the results
SELECT
id int,
FirstName1 as FirstName,
LastName1 as LastName,
UniueNumber1 AS SSN
FROM #data
UNION
SELECT
id int,
FirstName2 as FirstName,
LastName2 as LastName,
UniueNumber2 AS SSN
FROM #data
UNION
...
There's not a way to cleanly "loop through" the 7 groups of columns - you'll spend more time building a loop to create the query dynamically than just copying and pasting the query 6 times and changing the number.
Of course, it's best to avoid the type of structure you have in #data now if at all possible.

SELECT From Multiple tables with many to many relations

Hello everyone I have inherirted a poorly designed database and I need to get some information from 3 tables
Franchise
Id(Int, PK)
FrID (varchar(50))
FirstName (varchar(50))
LastName (varchar(50))
Store
Id (Int, PK)
FrID (varchar(50))
StoreNumber (varchar(50))
StoreName
Address
Pricing
Id (int, PK)
StoreNumber (varchar(50))
Price1
Price2
Price3
and the data
ID, FrID ,FirstName,LastName
1, 10 ,John Q , TestCase
2, 10 ,Jack Q , TestCase
3, 11 ,Jack Q , TestCase
ID, FrID, StoreNumber , StoreName , Address
10, 10 , 22222 , TestStore1, 123 Main street
11, 10 , 33333 , TestStore2, 144 Last Street
12, 10 , 44444 , TestStore2, 145 Next Street
13, 11 , 55555 , Other Test, 156 Other st
ID, StoreNumber, Price1, Price2, Price3
1, 22222 , 19.99, 20.99 , 30.99
2, 33333 , 19.99, 20.99 , 30.99
3, 44444 , 19.99, 20.99 , 30.99
4, 55555 , 19.99, 20.99 , 30.99
Here is what I have done
SELECT F.FirstName,F.LastName,F.FrID , S.StoreNumber,S.StoreName,S.Address,
P.Price1,P.Price2,P.Price3
FROM Franchisee F
JOIN Store S on F.FrID = S.FrID
JOIN Pricing P on P.StoreNumber = S.StoreNumber
This part works, but I end up with lots of duplicates, For example Jack Q gets listed for his store plus every store that John Q is on. Is there anyway to fix this with out a database redesign.
Okay, there is a whole laundry list of issues such as character fields such as [FrId] being used as strings, reserved words such as [address] being used as name, etc.
Let's put the bad design issues aside.
First, I need to create a quick test environment. I did not put in Foreign Keys since that constraint is not needed to get the correct answer.
--
-- Setup test tables
--
-- Just playing
use Tempdb;
go
-- drop table
if object_id('franchise')> 0
drop table franchise;
go
-- create table
create table franchise
(
Id int primary key,
FrID varchar(50),
FirstName varchar(50),
LastName varchar(50)
);
-- insert data
insert into franchise values
( 1, 10, 'John Q', 'TestCase'),
( 2, 10, 'Jack Q', 'TestCase'),
( 3, 11, 'Jack Q', 'TestCase');
-- select data
select * from franchise;
go
-- drop table
if object_id('store')> 0
drop table store;
go
-- create table
create table store
(
Id int primary key,
FrID varchar(50),
StoreNumber varchar(50),
StoreName varchar(50),
Address varchar(50)
);
-- insert data
insert into store values
(10, 10, 22222, 'TestStore1', '123 Main street'),
(11, 10, 33333, 'TestStore2', '144 Last Street'),
(12, 10, 44444, 'TestStore2', '145 Next Street'),
(13, 11, 55555, 'Other Test', '156 Other Street');
-- select data
select * from store;
go
-- drop table
if object_id('pricing')> 0
drop table pricing;
go
-- create table
create table pricing
(
Id int primary key,
StoreNumber varchar(50),
Price1 money,
Price2 money,
Price3 money
);
-- insert data
insert into pricing values
(1, 22222, 19.99, 20.99 , 30.99),
(2, 33333, 19.99, 20.99 , 30.99),
(3, 44444, 19.99, 20.99 , 30.99),
(4, 55555, 19.95, 20.95 , 30.95);
-- select data
select * from pricing;
go
The main issue is that the franchise table should have the primary key (PK) on FrId, not Id. I do not understand why there are duplicates.
However, the query below removes them by grouping. I changed the pricing data for Jack Q to show it is a different record.
--
-- Fixed Query - Version 1
--
select
f.FirstName,
f.LastName,
f.FrID,
s.StoreNumber,
s.StoreName,
s.Address,
p.Price1,
p.Price2,
p.Price3
from
-- Remove duplicates from francise
(
select
LastName,
FirstName,
Max(FrID) as FrID
from
franchise
group by
LastName,
FirstName
) as f
join store s on f.FrID = s.FrID
join pricing p on p.StoreNumber = s.StoreNumber;
The correct output is below.
If I am correct, remove the duplicates entries and change the primary key.
Change Requirements
Okay, you are placing two or more owners in the same table.
Below uses a sub query to combine the owners list into one string. Another way is to have a flag called primary owner. Choose that as the display name.
--
-- Fixed Query - Version 2
--
select
f.OwnersList,
f.FrID,
s.StoreNumber,
s.StoreName,
s.Address,
p.Price1,
p.Price2,
p.Price3
from
-- Compose owners list
(
select
FrID,
(
SELECT FirstName + ' ' + LastName + ';'
FROM franchise as inner1
WHERE inner1.FrID = outer1.FrID
FOR XML PATH('')
) as OwnersList
from franchise as outer1
group by FrID
) as f (FrId, OwnersList)
join store s on f.FrID = s.FrID
join pricing p on p.StoreNumber = s.StoreNumber
Here is the output from the second query.

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 HierarchyId How do I get the last descendants?

Using t-sql hierarchy Id how do I get all of the rows that have no children (that is the last decendants)?
Say my table is structured like this:
Id,
Name,
HierarchyId
And has these rows:
1, Craig, /
2, Steve, /1/
3, John, /1/1/
4, Sam, /2/
5, Matt, /2/1/
6, Chris, /2/1/1/
What query would give me John and Chris?
Perhaps there are better ways but this seams to do the job.
declare #T table
(
ID int,
Name varchar(10),
HID HierarchyID
)
insert into #T values
(1, 'Craig', '/'),
(2, 'Steve', '/1/'),
(3, 'John', '/1/1/'),
(4, 'Sam', '/2/'),
(5, 'Matt', '/2/1/'),
(6, 'Chris', '/2/1/1/')
select *
from #T
where HID.GetDescendant(null, null) not in (select HID
from #T)
Result:
ID Name HID
----------- ---------- ---------------------
3 John 0x5AC0
6 Chris 0x6AD6
Update 2012-05-22
Query above will fail if node numbers is not in an unbroken sequence. Here is another version that should take care of that.
declare #T table
(
ID int,
Name varchar(10),
HID HierarchyID
)
insert into #T values
(1, 'Craig', '/'),
(2, 'Steve', '/1/'),
(3, 'John', '/1/1/'),
(4, 'Sam', '/2/'),
(5, 'Matt', '/2/1/'),
(6, 'Chris', '/2/1/2/') -- HID for this row is changed compared to above query
select *
from #T
where HID not in (select HID.GetAncestor(1)
from #T
where HID.GetAncestor(1) is not null)
Since you only need leafs and you don't need to get them from a specific ancestor, a simple non-recursive query like this should do the job:
SELECT * FROM YOUR_TABLE PARENT
WHERE
NOT EXISTS (
SELECT * FROM YOUR_TABLE CHILD
WHERE CHILD.HierarchyId = PARENT.Id
)
In plain English: select every row without a child row.
This assumes your HierarchyId is a FOREIGN KEY towards the Id, not the whole "path" as presented in your example. If it isn't, this is probably the first thing you should fix in your database model.
--- EDIT ---
OK, here is the MS SQL Server-specific query that actually works:
SELECT * FROM YOUR_TABLE PARENT
WHERE
NOT EXISTS (
SELECT * FROM YOUR_TABLE CHILD
WHERE
CHILD.Id <> PARENT.Id
AND CHILD.HierarchyId.IsDescendantOf(PARENT.HierarchyId) = 1
)
Note that the IsDescendantOf considers any row a descendant of itself, so we also need the CHILD.Id <> PARENT.Id in the condition.
Hi I use this one and works perfectly for me.
CREATE TABLE [dbo].[Test]([Id] [hierarchyid] NOT NULL, [Name] [nvarchar](50) NULL)
DECLARE #Parent AS HierarchyID = CAST('/2/1/' AS HierarchyID) -- Get Current Parent
DECLARE #Last AS HierarchyID
SELECT #Last = MAX(Id) FROM Test WHERE Id.GetAncestor(1) = #Parent -- Find Last Id for this Parent
INSERT INTO Test(Id,Name) VALUES(#Parent.GetDescendant(#Last, NULL),'Sydney') -- Insert after Last Id

Help with recursive CTE query joining to a second table

My objective is to recurse through table tbl and while recursing through that table select a country abbreviation (if it exists) from another table tbl2 and append those results together which are included in the final output.
The example I'll use will come from this post
tbl2 has a Foreign Key 'tbl_id' to tbl and looks like this
INSERT INTO #tbl2( Id, Abbreviation, tbl_id )
VALUES
(100, 'EU', 1)
,(101, 'AS', 2)
,(102, 'DE', 3)
,(103, 'CN', 5)
*Note: not all the countries have abbreviations.
The trick is, I want all the countries in Asia to at least show the abbreviation of Asia which is 'AS' even if a country doesn't have an abbreviation (like India for example). If the country does have an abbreviation the result needs to look like this: China:CN,AS
I've got it partly working using a subquery, but India always returns NULL for the abbreviation. It's acting like if there isn't a full recursive path back to the abbreviation, then it returns null. Maybe the solution is to use a left outer join on the abbreviation table? I've tried for hours many different variations and the subquery is as close as I can get.
WITH abcd
AS (
-- anchor
SELECT id, [Name], ParentID,
CAST(([Name]) AS VARCHAR(1000)) AS "Path"
FROM #tbl
WHERE ParentId IS NULL
UNION ALL
--recursive member
SELECT t.id, t.[Name], t.ParentID,
CAST((a.path + '/' + t.Name + ':' +
(
select t2.abbreviation + ','
from #tbl2
where t.id = t2.id
)) AS VARCHAR(1000)) AS "Path"
FROM #tbl AS t
JOIN abcd AS a
ON t.ParentId = a.id
)
SELECT * FROM abcd
btw, I'm using sql server 2005 if that matters
Try this example, which will give you the output (1 sample row)
id Name ParentID Path abbreviation (No column name)
5 China 2 Asia/China CN,AS Asia/China:CN,AS
The TSQL being
DECLARE #tbl TABLE (
Id INT
,[Name] VARCHAR(20)
,ParentId INT
)
INSERT INTO #tbl( Id, Name, ParentId )
VALUES
(1, 'Europe', NULL)
,(2, 'Asia', NULL)
,(3, 'Germany', 1)
,(4, 'UK', 1)
,(5, 'China', 2)
,(6, 'India', 2)
,(7, 'Scotland', 4)
,(8, 'Edinburgh', 7)
,(9, 'Leith', 8)
;
DECLARE #tbl2 table (id int, abbreviation varchar(10), tbl_id int)
INSERT INTO #tbl2( Id, Abbreviation, tbl_id )
VALUES
(100, 'EU', 1)
,(101, 'AS', 2)
,(102, 'DE', 3)
,(103, 'CN', 5)
;WITH abbr AS (
SELECT a.*, isnull(b.abbreviation,'') abbreviation
FROM #tbl a
left join #tbl2 b on a.Id = b.tbl_id
), abcd AS (
-- anchor
SELECT id, [Name], ParentID,
CAST(([Name]) AS VARCHAR(1000)) [Path],
cast(abbreviation as varchar(max)) abbreviation
FROM abbr
WHERE ParentId IS NULL
UNION ALL
--recursive member
SELECT t.id, t.[Name], t.ParentID,
CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) [Path],
isnull(nullif(t.abbreviation,'')+',', '') + a.abbreviation
FROM abbr AS t
JOIN abcd AS a
ON t.ParentId = a.id
)
SELECT *, [Path] + ':' + abbreviation
FROM abcd