Merge 2 table-valued-function into one table with different column - sql

To start, I have a stringSplit() tabled-value function.
Suppose I have these as parameters:
var_1 = 'Apple.jpg,Carrot.png,Fruits.pdf'
var_2 = '9V0I1fab\CvaA5h,IV0asdkas//bVasA,Uasdl00/9asA' //Example base64 formats
How would I go so that after using stringSplit(var_1, ',') and stringSplit(var_2, ',')? It should merge as one table with different columns,
Say:
| name | b64 |
---------------------------------
| Apple.jpg | 9V0I1fab\CvaA5h |
| Carrot.png | IV0asdkas//bVasA |
| Fruits.pdf | Uasdl00/9asA |

Unfortunately, string_split() does not guarantee the ordering of the return values. Nor does it have an option for returning an index number.
If the elements in each string are unique, you can do:
select s1.value as name, s2.value as b64
from (select s.value,
row_number() over (order by charindex(',' + s.value + ',', ',' + #var_1 + ',')) as seqnum
from string_split(#var_1, ',') s
) s1 join
(select s.value,
row_number() over (order by charindex(',' + s.value + ',', ',' + #var_2 + ',')) as seqnum
from string_split(#var_2, ',') s
) s2
on s1.seqnum = s2.seqnum;
Here is a db<>fiddle.

Related

Select Item in String List In SQL Based On Value in Column

I have a SQL table with a column (Grouped_Identifer) that has a string list. The items on the list are separated by commas. There is another column (Position) which has a value. I would like to create or return a column let's call it Position_Identifer that has the portion or item in the Grouped_Identifer which correspond to the value in the Position column.
So we start with the following table:
And would like to end up with a table that looks like the following:
The string list in the Grouped_Identifier column can vary in the number (up to 20) of items.
Unfortunately, SQL Server does not provide the position in string_split(). And, there is no guarantee on the ordering of results from the function.
If the string has no duplicates, you can use charindex() to find the position:
select t.*, s.value
from t outer apply
(select *
from (select s.value,
row_number() over (order by charindex(',' + s.value + ',', ',' + t.gi + ',')) as seqnum
from string_split(t.gi, ',') s
) s
where seqnum = t.pos
) s;
Here is a db<>fiddle.
The new column can be calculated with:
select substring(Grouped_Identifier,Position*15-14,14) as Position_Identifier from yourTable
you can achieve this using string_split if your sql version supports this as follows
with data
as (select '008300000;#61,008300000;#62,008300000;#63' as gi,'1' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'2' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'3' as pos
)
,cte_d
as(
select *
,row_number() over(partition by pos order by pos) as rn
from data d
cross apply string_split(d.gi,',') x
)
select *
from cte_d
where rn=pos
In older versions you may use this
with data
as (select '008300000;#61,008300000;#62,008300000;#63' as gi,'1' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'2' as pos union all
select '008300000;#61,008300000;#62,008300000;#63' as gi,'3' as pos
)
,cte_d
as(
SELECT a.gi
,a.pos
,split.a.value('.', 'VARCHAR(100)') AS split_val
,row_number() over(partition by pos order by pos) as rn
FROM (SELECT pos
,CAST ('<M>' + REPLACE(gi, ',', '</M><M>') + '</M>' AS XML) AS col
,gi
FROM data
) a
CROSS APPLY col.nodes ('/M') AS split(a)
)
select *
from cte_d
where rn=pos
+-------------------------------------------+-----+---------------+----+
| gi | pos | value | rn |
+-------------------------------------------+-----+---------------+----+
| 008300000;#61,008300000;#62,008300000;#63 | 1 | 008300000;#61 | 1 |
| 008300000;#61,008300000;#62,008300000;#63 | 2 | 008300000;#62 | 2 |
| 008300000;#61,008300000;#62,008300000;#63 | 3 | 008300000;#63 | 3 |
+-------------------------------------------+-----+---------------+----+
dbfiddle link
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=1f753ad7c97255351efc704ebdd966c3

Extract a part of string between multiple delimiters

I have a column with value as '/1064_MyHoldings/ONLINE/Adhoc/Rpt_CompanyCodeElig'
Now, my requirement is to extract every value which is there between the delimeters; '1064 MyHoldings', 'ONLINE', 'Adhoc' etc?
I tried the below code, but it is only taking '1064 MyHoldings'. But I need the other values as well
Can someone please help me here?
WITH yourTable AS (
SELECT '/1064_MyHoldings/ONLINE/Adhoc/Rpt_CompanyCodeElig' AS Path
)
SELECT
CASE WHEN Path LIKE '%/%/%' THEN
SUBSTRING(Path,
CHARINDEX('/', Path) + 1,
CHARINDEX('/', Path, CHARINDEX('/', Path) + 1) - CHARINDEX('/', Path) - 1)
ELSE 'NA' END AS first_component
FROM yourTable;
Use string_split():
select s.value
from t cross apply
string_split(path, '/') s
You can go for recursive search using CTE and split the strings.
WITH yourTable AS (
SELECT '/1064_MyHoldings/ONLINE/Adhoc/Rpt_CompanyCodeElig' AS Path
),
cte_splitTable as
(
SELECT value as val, 1 as lvl
from yourTable
cross apply
string_split(Path,'_')
UNION ALL
SELECT t.value as val, lvl+1 as lvl
from cte_splitTable as c
cross apply
string_split(c.val,'/') as t
where CHARINDEX('/',val) > 0
)
select * from cte_splitTable
where PATINDEX('%[_/]%',val) = 0 and len(val) > 0
+-----------------+
| val |
+-----------------+
| CompanyCodeElig |
| MyHoldings |
| ONLINE |
| Adhoc |
| Rpt |
| 1064 |
+-----------------+

String split on multiple fields to one result

I have a table that looks like the below set as a field for one value:
|---------------------|------------------|------------------|
| Colour | Amount | Size |
|---------------------|------------------|------------------|
| Black,Blue,Green | 1,2,2 | 100,100,100 |
|---------------------|------------------|------------------|
I need to do a string split on each of them and return it in one go.
I've currently got this and works for colour:
SELECT value as colour
FROM [table_name]
CROSS APPLY STRING_SPLIT(colour, ',')
I can't figure out how to do multiple string splits in one go. It should return it as this then:
|---------------------|------------------|------------------|
| Colour | Amount | Size |
|---------------------|------------------|------------------|
| Black | 1 | 100 |
|---------------------|------------------|------------------|
| Blue | 2 | 100 |
|---------------------|------------------|------------------|
| Green | 2 | 100 |
|---------------------|------------------|------------------|
Any help would be great!
Unfortunately, string_split() doesn't provide the option to preserve the order of the substrings it produces. Hence, that is very, very tricky to use in this case.
I prefer a recursive CTE (until the function gets fixed):
with cte as (
select convert(varchar(max), null) as color,
convert(varchar(max), null) as amount,
convert(varchar(max), null) as size,
convert(varchar(max), colors + ',') as rest_colors,
convert(varchar(max), amounts + ',') as rest_amounts ,
convert(varchar(max), sizes + ',') as rest_sizes,
0 as lev
from t
union all
select left(rest_colors, charindex(',', rest_colors) - 1),
left(rest_amounts, charindex(',', rest_amounts) - 1),
left(rest_sizes, charindex(',', rest_sizes) - 1),
stuff(rest_colors, 1, charindex(',', rest_colors), ''),
stuff(rest_amounts, 1, charindex(',', rest_amounts), ''),
stuff(rest_sizes, 1, charindex(',', rest_sizes), ''),
lev + 1
from cte
where rest_colors <> ''
)
select color, amount, size
from cte
where lev > 0;
Here is a db<>fiddle.
As Gordon mentioned, string_split() does not GTD sequence. That said, and if you are open to a Table-Value Function, consider the following where we UNPIVOT your data and then apply a final PIVOT. Note: The RN = ... could be replaced with your own ID (if you have one)
I adjusted the values to illustrate there is a proper sequencing.
Example
;with cte as (
Select RN = row_number() over (order by (select null))
,[Colour]
,[Amount]
,[Size]
From YourTable
)
Select *
From (
Select RN,Item='Colour',B.* From cte Cross Apply [dbo].[tvf-Str-Parse](Colour,',') B
Union All
Select RN,Item='Amount',B.* From cte Cross Apply [dbo].[tvf-Str-Parse](Amount,',') B
Union All
Select RN,Item='Size' ,B.* From cte Cross Apply [dbo].[tvf-Str-Parse](Size ,',') B
) src
Pivot ( max(RetVal) for Item in ([Colour],[Amount],[Size] ) ) pvt
Returns
RN RetSeq Colour Amount Size
1 1 Black 1 100
1 2 Blue 2 200
1 3 Green 3 300
The Function if Interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);

SQL: How to concatenate every group of N rows into a single row

I have this
ID | Name
----+-------
31 | Abby
24 | Bruce
44 | Carl
49 | Derek
55 | Eric
81 | Fred
I want to concatenate groups of N rows into a single row. For N = 3, this would give me this
ID | Name
----------+----------------
31,24,44 | Abby,Bruce,Carl
49,55,81 | Derek,Eric,Fred
I managed to generate a row to use GROUP BY and CONCAT on, but it only works in mysql...
SET #row_number = 0;
SELECT *, (#row_number:=#row_number + 1) AS r1, (#row_number - 1) DIV 3 AS r2 FROM table1
ID | Name | r1| r2
----+-------+---+---
31 | Abby | 1 | 0
24 | Bruce | 2 | 0
44 | Carl | 3 | 0
49 | Derek | 4 | 1
55 | Eric | 5 | 1
81 | Fred | 6 | 1
For clarification:
I want a vanilla-like SQL solution (So it will work in mysql, sybase, oracle and postgres)
I don't need any order, I just want to reconstitute the original table at some point
I don't have writing privileges on this base, only reading
I want to concatenate any columns type (by casting them to a string) and handle NULLs
It's ok if some groups are not exactly of size N (like the last one)
The standard SQL solution looks something like this:
select listagg(id, ',') within group (order by id) as ids,
listagg(name, ',') within group (order by id) as names
from (select t.*, row_number() over (order by id) as seqnum
from t
) t
group by cast( (seqnum - 1) / 3 as int);
I think this will work as-is in Oracle. In MySQL, you need to change listagg() to group_concat() (and be using MySQL 8+) and in Postgres, you need to change listagg() to string_agg().
And, you pretty much can't do this easily in Sybase.
Oh, wait, there is another way:
select concat( (case when seqnum % 3 = 1 then concat(id, ';') else '' end),
(case when seqnum % 3 = 2 then concat(id, ';') else '' end),
(case when seqnum % 3 = 0 then concat(id, ';') else '' end)
) as ids,
concat( (case when seqnum % 3 = 1 then concat(name, ';') else '' end),
(case when seqnum % 3 = 2 then concat(name, ';') else '' end),
(case when seqnum % 3 = 0 then concat(name, ';') else '' end)
) as name
from (select t.*, row_number() over (order by id) as seqnum
from t
) t
group by cast( (seqnum - 1) / 3 as int);
Of course, Sybase doesn't support concat(), so you have to use +. And this produces ; for the separator rather than ,, but it is pretty close.
Amazing script. But I suspect you left out an AS. So I made it like so:
select string_agg(t.[id], ',') as ids,
string_agg(t.[name], ',') as names
from
(select t.*, row_number() over (order by id) as seqnum
from [tablename]
) AS t
group by cast( (seqnum - 1) / 3 as int);
In my case, it is as such (though I could not get the 'within group (order by id)' to work at any way..... hmmmm)
Here is mine that works well, which is a list of emails to all my students, combined into a row for every 100 rows. The String_Agg limits it to 8000 character, sadly. Anyone knows any alternative to String_Agg for an SQL Server?
SELECT
string_agg(t.[Student Name], ';') as [All Names],
string_agg(t.[Student Email], ';') as [All Emails]
FROM
(
SELECT [Student Name], [Student Email], ROW_NUMBER() OVER (ORDER BY [Student Email]) AS RowNo
FROM [Mailing List For Courses] where [Prod Name]='Online Courses'
) AS t
group by cast( (RowNo - 1) / 100 as int);
Hope it helps <3

Generate list number

I have a table look like this:
+-------+--------+--------+
| Grp | Party | Member |
+-------+--------+--------+
| FC | Party1 | Tom |
| FC | Party1 | Alice |
| FC | Party2 | John |
| FC | Party3 | Mary |
| GC | Party2 | Anna |
| GC | Party4 | Alex |
| GC | Party5 | Diana |
+-------+--------+--------+
I want to transform the table into list like this:
+-------+--------+
| ID | Text |
+-------+--------+
| 1 | FC |
| 1.1 | Party1 |
| 1.1.1 | Tom |
| 1.1.2 | Alice |
| 1.2 | Party2 |
| 1.2.1 | John |
| 1.3 | Party3 |
| 1.3.1 | Mary |
| 2 | GC |
| 2.1 | Party2 |
| 2.1.1 | Anna |
| 2.2 | Party4 |
| 2.2.1 | Alex |
| 2.3 | Party5 |
| 2.3.1 | Diana |
+-------+--------+
I have tried rollup with row_number, but the result still far away what I want
;with ctx as (
select * from #test
group by rollup(Grp, Party, Member)
)
select row_number() over (partition by grp order by grp, party, member) as g,
row_number() over (partition by grp, party order by grp, party, member) as p,
row_number() over (partition by grp, party, member order by grp, party, member) as m,
grp, party, member
from ctx
where grp is not null
order by grp, party, member
Thanks in advance.
EDIT
Here is the SQL to generate the table, hope this can help
declare #test table (Grp varchar(10), Party varchar(10), Member varchar(20))
insert into #test values ('FC', 'Party1', 'Tom')
insert into #test values ('FC', 'Party1', 'Alice')
insert into #test values ('FC', 'Party2', 'John')
insert into #test values ('FC', 'Party3', 'Mary')
insert into #test values ('GC', 'Party2', 'Anna')
insert into #test values ('GC', 'Party4', 'Alex')
insert into #test values ('GC', 'Party5', 'Diana')
This uses DENSE_RANK to get the correct numbering for the ID. Then CROSS APPLY to unpivot the data and mark which row is for the Grp, Party, or Member. Finally use WHERE to filter only those rows you need:
WITH CteUnpivot AS(
SELECT *
FROM (
SELECT *,
rnGrp = DENSE_RANK() OVER(ORDER BY Grp),
rnParty = DENSE_RANK() OVER(PARTITION BY Grp ORDER BY Party),
rnMember = ROW_NUMBER() OVER(PARTITION BY Grp, Party ORDER BY Member)
FROM test
) t
CROSS APPLY(VALUES
('Grp', Grp),
('Party', Party),
('Member', Member)
) x (col, [Text])
)
SELECT
ID = CASE
WHEN col = 'Grp' THEN CAST(rnGrp AS VARCHAR(3))
WHEN col = 'Party' THEN CAST(rnGrp AS VARCHAR(3)) + '.' + CAST(rnParty AS VARCHAR(3))
WHEN col = 'Member' THEN CAST(rnGrp AS VARCHAR(3)) + '.' + CAST(rnParty AS VARCHAR(3)) + '.' + CAST(rnMember AS VARCHAR(3))
END,
[Text]
FROM CteUnpivot
WHERE
(col = 'Grp' AND rnParty = 1 AND rnMember = 1)
OR (col = 'Party' AND rnMember = 1)
OR (col = 'Member')
ORDER BY rnGrp, rnParty, rnMember;
ONLINE DEMO
If order does not matter for Member, replace rnMember with:
rnMember = ROW_NUMBER() OVER(PARTITION BY Grp, Party ORDER BY (SELECT NULL))
ONLINE DEMO
Here is one way
;WITH cte
AS (SELECT Dense_rank()OVER (ORDER BY grp) AS g,
Dense_rank()OVER (partition BY grp ORDER BY party) AS p,
Row_number()OVER (partition BY grp, party ORDER BY member) AS m,
grp,
party,
member
FROM #test
WHERE grp IS NOT NULL)
SELECT DISTINCT grp,
Cast(g AS VARCHAR(10)) AS [Text]
FROM cte
UNION ALL
SELECT DISTINCT party,
Concat(g, '.', p)
FROM cte
UNION ALL
SELECT member,
Concat(g, '.', p, '.', m)
FROM cte
ORDER BY [Text]
You need to use DENSE_RANK for parents to generate hierarchy numbers properly. If you have duplicates in Member as well then change the ROW_NUMBER to DENSE_RANK inside CTE and add distinct to the final select query
Note : If you are using anything less than SQL SERVER 2012 then use + operator for concatenation instead of CONCAT
Frankly, I would not do this at the database level. Instead I would ensure the output is sorted by {Grp, Party, Member} and then assign "Id" values in a single pass through as you display the data.
However, if you're determined to do this in the database server for whatever reason, you could use the dense_rank() function to get each individual id:
;with cte as (
select dense_rank() over (order by Grp) id0,
dense_rank() over (partition by Grp order by Party) id1,
dense_rank() over (partition by Grp, Party order by Member) id2,
Grp, Party, Member
from Table1
), grps as (select distinct id0, Grp from cte),
parties as (select distinct id0, id1, Party from cte),
members as (select distinct id0, id1, id2, Member from cte),
[list] as (
select cast(id0 as varchar(50)) as id, Grp as [Text] from grps
union all
select cast(id0 as varchar(50)) + '.' + cast(id1 as varchar(50)), Party from parties
union all
select cast(id0 as varchar(50)) + '.' + cast(id1 as varchar(50)) + '.' + cast(id2 as varchar(50)), Member from members
)
select id, [Text]
from [list]
order by id
This option doesn't use DENSE_RANK() but ROW_NUMBER() but is essentially similar to other answers posted.
With grps As (
Select Grp, GrpNo = Row_Number() Over (Order By Grp)
From (Select Distinct Grp From MyTable) As MyTable),
parties As (
Select MyTable.Grp, MyTable.Party, grps.GrpNo, PrtyNo = Row_Number() Over (Partition By MyTable.Grp Order By MyTable.Party)
From (Select Distinct Grp, Party From MyTable) As MyTable
Join grps On MyTable.Grp = grps.Grp),
members As (
Select MyTable.Grp, MyTable.Party, MyTable.Member,
parties.GrpNo, parties.PrtyNo, MbrNo = Row_Number() Over (Partition By MyTable.Grp, MyTable.Party Order By #groups.Member)
From MyTable
Join parties On MyTable.Grp = parties.Grp And MyTable.Party = parties.Party)
Select ID = Convert(char(5), GrpNo),
[Text] = Grp
From grps
Union All
Select ID = Convert(char(1), GrpNo) + '.' + Convert(char(1), PrtyNo),
[Text] = Party
From parties
Union All
Select ID = Convert(char(1), GrpNo) + '.' + Convert(char(1), PrtyNo) + '.' + Convert(char(1), MbrNo),
[Text] = Member
From members;