SQL join tables and CASE with CONCAT - sql

I'm a SQL newb... and I need to join two tables (see below)
Table A
| id | Recipe_Web_Codes |
|----|--------------------|
| 1 | GF VGT |
| 2 | |
| 3 | VGN |
Table B
| id | Recipe_Web_Code | Webcode_Fullname | Color |
|----|-----------------|------------------------|---------|
| 1 | VGT | Vegetarian | #ff6038 |
| 2 | VGN | Vegan Friendly | #97002d |
| 3 | GF | Gluten Friendly | #6ca4b6 |
and produce the following table:
RESULT
| id | Recipe_Web_Codes | Wecode_Fullname | Color |
|-------------------------------------------------------|------------------|
| 1 | GF VGT | Gluten Friendly, Vegetarian | #6ca4b6, #ff6038 |
| 2 | | | |
| 3 | VGN | Vegan Friendly | #97002d |
I honestly don't know where to begin. I tried this but got stuck on how to concatenate case results into a single field. Am I on the right track?
select Recipe_Web_Codes, Webcode_Fullname =
case
when Recipe_Web_Codes like '%VGT%' then (select Webcode_Fullname FROM TABLE_B where Recipe_Web_Code = 'VGT')
when Recipe_Web_Codes like '%VGN%' then (select Webcode_Fullname FROM TABLE_B where Recipe_Web_Code = 'VGN')
when Recipe_Web_Codes like '%GF%' then (select Webcode_Fullname FROM TABLE_B where Recipe_Web_Code = 'GF')
end,
Color =
case
when Recipe_Web_Codes like '%VGT%' then (select Color FROM TABLE_B where Recipe_Web_Code = 'VGT')
when Recipe_Web_Codes like '%VGN%' then (select Color FROM TABLE_B where Recipe_Web_Code = 'VGN')
when Recipe_Web_Codes like '%GF%' then (select Color FROM TABLE_B where Recipe_Web_Code = 'GF')
end
from TABLE_A
EDIT: It just clicked on me that I missed a very important point as to why I need to aggregate these strings. The resulting table is going to be exported to JSON by another separate process, so no point to mention database normalization. Also, this is SQL 2016 SP2 so I don't have the fuction for String_agg available.

You should not be storing multiple values in a single column, so I would advise you to fix your data model.
That said, you can do what you want by pulling apart the strings and re-aggregating:
select a.*, b.*
from a outer apply
(select string_agg(b.Webcode_Fullname, ',') as Webcode_Fullname,
string_agg(b.Webcode_Fullname, ',') as Colors
from string_split(a.recipe_web_codes, ' ') s join
b
on s.value = b.Recipe_Web_Code
) b;
Very important: string_split() does not guarantee the ordering of the values. If the ordering of the resulting strings is important, you can handle this -- assuming you have no duplicates -- by using logic such as:
select a.*, b.*
from a outer apply
(select string_agg(b.Webcode_Fullname, ',') within group (order by charindex(b.Recipe_Web_Codeas, a.recipe_web_codes)) as Webcode_Fullname,
string_agg(b.Webcode_Fullname, ',') within group (order by charindex(b.Recipe_Web_Codeas, a.recipe_web_codes)) as Colors
from string_split(a.recipe_web_codes, ' ') s join
b
on s.value = b.Recipe_Web_Code
) b;
Let me emphasize again that you should put your effort into fixing your data model, by having a separate table for the recipe web codes, with one row per code.
EDIT:
One solution for older versions is a recursive CTE:
with bs as (
select b.*, row_number() over (order by id) as seqnum
from b
),
cte as (
select a.id, convert(varchar(max), Recipe_Web_Codes) as fullnames, convert(varchar(max), Recipe_Web_Codes) as colors, 1 as ind
from a
union all
select cte.id,
replace(cte.fullnames, bs.Recipe_Web_Code, bs.Webcode_Fullname),
replace(cte.colors, bs.Recipe_Web_Code, bs.color),
1 + cte.ind
from cte join
bs
on cte.ind = bs.seqnum and ind < 10
)
select cte.id, cte.fullnames, cte.colors
from (select cte.*, max(ind) over (partition by id) as max_ind
from cte
) cte
where ind = max_ind ;
Here is a db<>fiddle.

You can do it with a left join of the tables and the aggregate function string_agg() (works in SQL Server 2017+):
select a.id, a.Recipe_Web_Codes,
string_agg(b.Webcode_Fullname, ', ') Webcode_Fullname,
string_agg(b.Color, ', ') Color
from Table_A a left join Table_B b
on ' ' + a.Recipe_Web_Codes + ' ' like '% ' + b.Recipe_Web_Code + ' %'
group by a.id, a.Recipe_Web_Codes
order by a.id
See the demo.
Results:
> id | Recipe_Web_Codes | Webcode_Fullname | Color
> -: | :--------------- | :-------------------------- | :---------------
> 1 | GF VGT | Vegetarian, Gluten Friendly | #ff6038, #6ca4b6
> 2 | null | null | null
> 3 | VGN | Vegan Friendly | #97002d

Related

Postgres group by empty string question to include empty string in output

I have following table in Postgres
| phone | group | spec |
| 1 | 1 | 'Lock' |
| 1 | 2 | 'Full' |
| 1 | 3 | 'Face' |
| 2 | 1 | 'Lock' |
| 2 | 3 | 'Face' |
| 3 | 2 | 'Scan' |
Tried this
SELECT phone, string_agg(spec, ', ')
FROM mytable
GROUP BY phone;
Need this ouput for each phone where there is empty string for missing group.
| phone | spec
| 1 | Lock, Full, Face
| 2 | Lock, '' , Face
| 3 | '', Scan ,''
You need a CTE which returns all possible combinations of phone and group and a left join to the table so you can group by phone:
with cte as (
select *
from (
select distinct phone from mytable
) m cross join (
select distinct "group" from mytable
) g
)
select c.phone, string_agg(coalesce(t.spec, ''''''), ',') spec
from cte c left join mytable t
on t.phone = c.phone and t."group" = c."group"
group by c.phone
See the demo.
Results:
| phone | spec |
| ----- | -------------- |
| 1 | Lock,Full,Face |
| 2 | Lock,'',Face |
| 3 | '',Scan,'' |
You can use conditional aggregation:
select phone,
(max(case when group = 1 then spec else '''''' end) || ', ' ||
max(case when group = 2 then spec else '''''' end) || ', ' ||
max(case when group = 3 then spec else '''''' end)
) as specs
from mytable t
group by phone;
Alternatively, you can general all the groups using generate_series() and then aggregation:
select p.phone,
string_agg(coalesce(t.spec, ''''''), ', ') as specs
from (select distinct phone from mytable) p cross join
generate_series(1, 3, 1) gs(grp) left join
mytable t
on t.phone = p.phone and t.group = gs.grp
group by p.phone
You can consider using a self - (RIGHT/LEFT)JOIN with all three distinct groups (which's stated within the subquery just after RIGHT JOIN keywords ) and a correlated query for your table :
WITH mytable1 AS
(
SELECT distinct t1.phone, t2."group",
( SELECT spec FROM mytable WHERE phone = t1.phone AND "group"=t2."group" )
FROM mytable t1
RIGHT JOIN ( SELECT distinct "group" FROM mytable ) t2
ON t2."group" = coalesce(t2."group",t1."group")
)
SELECT phone, string_agg(coalesce(spec,''''''), ', ') as spec
FROM mytable1
GROUP BY phone;
Demo

Multiple select from CTE with different number of rows in a StoredProcedure

How to do two select with joins from the cte's which returns total number of columns in the two selects?
I tried doing union but that appends to the same list and there is no way to differentiate for further use.
WITH campus AS
(SELECT DISTINCT CampusName, DistrictName
FROM dbo.file
),creditAcceptance AS
(SELECT CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal, COUNT(id) AS N
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%') AND (CollegeCreditEarnedFinal = 'Yes') AND (CollegeCreditAcceptedFinal = 'Yes')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
),eligibility AS
(SELECT CampusName, EligibilityStatusFinal, COUNT(id) AS N, CollegeCreditAcceptedFinal
FROM dbo.file
WHERE (EligibilityStatusFinal LIKE 'Eligible%')
GROUP BY CampusName, EligibilityStatusFinal, CollegeCreditAcceptedFinal
)
SELECT a.CampusName, c.[EligibilityStatusFinal], SUM(c.N) AS creditacceptCount
FROM campus as a FULL OUTER JOIN creditAcceptance as c ON a.CampusName=c.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName ,c.EligibilityStatusFinal
Union ALL
SELECT a.CampusName , b.[EligibilityStatusFinal], SUM(b.N) AS eligible
From Campus as a FULL OUTER JOIN eligibility as b ON a.CampusName = b.CampusName
WHERE (a.DistrictName = 'xy')
group by a.CampusName,b.EligibilityStatusFinal
Expected output:
+------------+------------------------+--------------------+
| CampusName | EligibilityStatusFinal | creditacceptCount |
+------------+------------------------+--------------------+
| M | G | 1 |
| E | NULL | NULL |
| A | G | 4 |
| B | G | 8 |
+------------+------------------------+--------------------+
+------------+------------------------+----------+
| CampusName | EligibilityStatusFinal | eligible |
+------------+------------------------+----------+
| A | G | 8 |
| C | G | 9 |
| A | T | 9 |
+------------+------------------------+----------+
As you can see here CTEs can be used in a single statement only, so you can't get the expected output with CTEs.
Here is an excerpt from Microsoft docs:
A CTE must be followed by a single SELECT, INSERT, UPDATE, or DELETE
statement that references some or all the CTE columns. A CTE can also
be specified in a CREATE VIEW statement as part of the defining SELECT
statement of the view.
You can use table variables (declare #campus table(...)) or temp tables (create table #campus (...)) instead.

Redshift create all the combinations of any length for the values in one column

How can we create all the combinations of any length for the values in one column and return the distinct count of another column for that combination?
Table:
+------+--------+
| Type | Name |
+------+--------+
| A | Tom |
| A | Ben |
| B | Ben |
| B | Justin |
| C | Ben |
+------+--------+
Output Table:
+-------------+-------+
| Combination | Count |
+-------------+-------+
| A | 2 |
| B | 2 |
| C | 1 |
| AB | 3 |
| BC | 2 |
| AC | 2 |
| ABC | 3 |
+-------------+-------+
When the combination is only A, there are Tom and Ben so it's 2.
When the combination is only B, 2 distinct names so it's 2.
When the combination is A and B, 3 distinct names: Tom, Ben, Justin so it's 3.
I'm working in Amazon Redshift. Thank you!
NOTE: This answers the original version of the question which was tagged Postgres.
You can generate all combinations with this code
with recursive td as (
select distinct type
from t
),
cte as (
select td.type, td.type as lasttype, 1 as len
from td
union all
select cte.type || t.type, t.type as lasttype, cte.len + 1
from cte join
t
on 1=1 and t.type > cte.lasttype
)
You can then use this in a join:
with recursive t as (
select *
from (values ('a'), ('b'), ('c'), ('d')) v(c)
),
cte as (
select t.c, t.c as lastc, 1 as len
from t
union all
select cte.type || t.type, t.type as lasttype, cte.len + 1
from cte join
t
on 1=1 and t.type > cte.lasttype
)
select type, count(*)
from (select name, cte.type, count(*)
from cte join
t
on cte.type like '%' || t.type || '%'
group by name, cte.type
having count(*) = length(cte.type)
) x
group by type
order by type;
There is no way to generate all possible combinations (A, B, C, AB, AC, BC, etc) in Amazon Redshift.
(Well, you could select each unique value, smoosh them into one string, send it to a User-Defined Function, extract the result into multiple rows and then join it against a big query, but that really isn't something you'd like to attempt.)
One approach would be to create a table containing all possible combinations — you'd need to write a little program to do that (eg using itertools in Python). Then, you could join the data against that reasonably easy to get the desired result (eg IF 'ABC' CONTAINS '%A%').

Oracle SQL Get unique symbols from table

I have table with descriptions of smth. For example:
My_Table
id description
================
1 ABC
2 ABB
3 OPAC
4 APEЧ
I need to get all unique symbols from all "description" columns.
Result should look like that:
symbol
================
A
B
C
O
P
E
Ч
And it shoud work for all languages, so, as I see, regular expressions cant help.
Please help me. Thanks.
with cte (c,description_suffix) as
(
select substr(description,1,1)
,substr(description,2)
from mytable
where description is not null
union all
select substr(description_suffix,1,1)
,substr(description_suffix,2)
from cte
where description_suffix is not null
)
select c
,count(*) as cnt
from cte
group by c
order by c
or
with cte(n) as
(
select level
from dual
connect by level <= (select max(length(description)) from mytable)
)
select substr(t.description,c.n,1) as c
,count(*) as cnt
from mytable t
join cte c
on c.n <= length(description)
group by substr(t.description,c.n,1)
order by c
+---+-----+
| C | CNT |
+---+-----+
| A | 4 |
| B | 3 |
| C | 2 |
| E | 1 |
| O | 1 |
| P | 2 |
| Ч | 1 |
+---+-----+
Create a numbers table and populate it with all the relevant ids you'd need (in this case 1..maxlength of string)
SELECT DISTINCT
locate(your_table.description, numbers.id) AS symbol
FROM
your_table
INNER JOIN
numbers
ON numbers.id >= 1
AND numbers.id <= CHAR_LENGTH(your_table.description)
SELECT DISTINCT(SUBSTR(ll,LEVEL,1)) OP --Here DISTINCT(SUBSTR(ll,LEVEL,1)) is used to get all distinct character/numeric in vertical as per requirment
FROM
(
SELECT LISTAGG(DES,'')
WITHIN GROUP (ORDER BY ID) ll
FROM My_Table --Here listagg is used to convert all values under description(des) column into a single value and there is no space in between
)
CONNECT BY LEVEL <= LENGTH(ll);

Get hierarchical structure using SQL Server

I have a self-referencing table with a primary key, id and a foreign key parent_id.
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PK | NULL | IDENTITY |
| parent_id | int(11) | YES | | NULL | |
| name | varchar(255) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
I have got a table as following (reduce data for more clear)
Table MySiteMap
Id Name parent_id
1 A NULL
2 B 1
3 C 1
4 D 1
20 B1 2
21 B2 2
30 C1 3
31 C2 3
40 D1 4
41 D2 4
I would like get the hierarchical structure using SQL Server query:
A
|
B
|
| B1
| B2
C
|
| C1
| C2
D
|
| D1
| D2
Any suggestions?
You can use Common Table Expressions.
WITH LeveledSiteMap(Id, Name, Level)
AS
(
SELECT Id, Name, 1 AS Level
FROM MySiteMap
WHERE Parent_Id IS NULL
UNION ALL
SELECT m.Id, m.Name, l.Level + 1
FROM MySiteMap AS m
INNER JOIN LeveledSiteMap AS l
ON m.Parent_Id = l.Id
)
SELECT *
FROM LeveledSiteMap
Use this:
;WITH CTE(Id, Name, parent_id, [Level], ord) AS (
SELECT
MySiteMap.Id,
CONVERT(nvarchar(255), MySiteMap.Name) AS Name,
MySiteMap.parent_id,
1,
CONVERT(nvarchar(255), MySiteMap.Id) AS ord
FROM MySiteMap
WHERE MySiteMap.parent_id IS NULL
UNION ALL
SELECT
MySiteMap.Id,
CONVERT(nvarchar(255), REPLICATE(' ', [Level]) + '|' + REPLICATE(' ', [Level]) + MySiteMap.Name) AS Name,
MySiteMap.parent_id,
CTE.[Level] + 1,
CONVERT(nvarchar(255),CTE.ord + CONVERT(nvarchar(255), MySiteMap.Id)) AS ord
FROM MySiteMap
JOIN CTE ON MySiteMap.parent_id =CTE.Id
WHERE MySiteMap.parent_id IS NOT NULL
)
SELECT Name
FROM CTE
ORDER BY ord
For this:
A
| B
| B1
| B2
| C
| C1
| C2
| D
| D1
| D2
I started with a query, (but when I check it now it is similar to Mark.)
I will add it anyway, while I created also a sqlfiddle with mine and Mark query.
WITH tList (id,name,parent_id,nameLevel)
AS
(
SELECT t.id, t.name, t.parent_id, 1 AS nameLevel
FROM t as t
WHERE t.parent_id IS NULL
UNION ALL
SELECT tnext.id, tnext.name, tnext.parent_id, tList.nameLevel + 1
FROM t AS tnext
INNER JOIN tList AS tlist
ON tnext.parent_id = tlist.id
)
SELECT id,name,isnull(parent_id,0) 'parent_id',nameLevel FROM tList order by nameLevel;
A good blog:
SQL Query – How to get data in Hierarchical Structure?
i know changing the structure of a table is always a critical operation but since sql server 2008 introduced the HierarchyId Datatype i really like workig with it. Maybe have a look at:
http://www.codeproject.com/Articles/37171/HierarchyID-Data-Type-in-SQL-Server
http://www.codeproject.com/Tips/740553/Hierarchy-ID-in-SQL-Server
I am sure you will understand quickly how to use this datatype and his functions. The SQL Code using this datatype is more structured and has better performance than CTE's.