Join together multiple columns split by a character in SQL

ε祈祈猫儿з 提交于 2020-06-26 12:59:29

问题


using mssql, if i have data such as:

cols: 
id, name,     list1,         list2
1, 'first',  '10;15;30;50', '25;12;15;18'
2, 'second', '50;30;15;10,  '12;25;11;15' 
...
10,'tenth',  '9;2;15;1',    '5;13;17;45'

im trying to create rows of results that join each of those list columns together, such as

1, 'first', 10, 25
1, 'first', 15, 12
1, 'first', 30, 15
1, 'first', 50, 18
2, 'second', 50, 12
2, 'second', 30, 25
2, 'second', 15, 11
2, 'second', 10, 15
...
10, 'tenth', 9, 5
10, 'tenth', 2, 13
10, 'tenth', 15, 17
10, 'tenth', 1, 45

basically, each number of each list maps to the same number at that index (split by ';'). i'm able to use cross apply + string_split, but it results in a row for each possible combination (id * description * list1_size * list2_size) is this even possible in sql?

I've also tried using substring + charindex to manually move around the lists, but this would result in an exorbitant amount of manual columns.


回答1:


If the list have equal size:

SELECT 1 AS id, 'first' AS name, '10;15;30;50' AS list1, '25;12;15;18' AS list2
INTO t
UNION ALL
SELECT 2, 'second', '50;30;15;10', '12;25;11;15';

-- a bit undeterministic, ROW_NUMBER ordered by placeholder 1/0
SELECT id, name, s1.value, s2.value
FROM t
CROSS APPLY (SELECT *, ROW_NUMBER() OVER(ORDER BY 1/0) AS r FROM STRING_SPLIT(list1, ';')) s1
CROSS APPLY (SELECT *, ROW_NUMBER() OVER(ORDER BY 1/0) AS r FROM STRING_SPLIT(list2, ';')) s2
WHERE s1.r = s2.r;

db<>fiddle demo

Related: STRING_SPLIT Add Option to Return Row Number


EDIT:

Using OPENJSON to get deterministic position of element in array:

SELECT id, name, A.value, B.value
FROM t
CROSS APPLY (SELECT value, [key] AS rn 
             FROM OPENJSON(JSON_QUERY(CONCAT('[',REPLACE(t.list1,';',','),']')))) A
CROSS APPLY (SELECT value, [key] AS rn 
             FROM OPENJSON(JSON_QUERY(CONCAT('[',REPLACE(t.list2,';',','),']')))) B
WHERE A.rn = B.rn;

db<>fiddle demo 2


EDIT 2:

Handling list of different sizes:

WITH cte1 AS (
  SELECT id, name, A.value, A.rn
  FROM t
  CROSS APPLY (SELECT value, [key] AS rn 
              FROM OPENJSON(JSON_QUERY(CONCAT('[',REPLACE(t.list1,';',','),']')))) A
),cte2 AS (
  SELECT id, name, A.value, A.rn
  FROM t
  CROSS APPLY (SELECT value, [key] AS rn 
              FROM OPENJSON(JSON_QUERY(CONCAT('[',REPLACE(t.list2,';',','),']')))) A
)
SELECT id = COALESCE(cte1.id, cte2.id)
       ,name = COALESCE(cte1.name, cte2.name)
       ,cte1.value
       ,cte2.value
FROM cte1
FULL JOIN cte2
  ON cte1.id = cte2.id
 AND cte1.rn = cte2.rn
ORDER BY id;

db<>fiddle demo 3




回答2:


As long as the lists don't contain more than 4 members, you can use PARSENAME() as a shortcut and cross join to a number-table or cte with just the numbers 1-4.

Something like this psuedo-code, assuming you have a Table called tblNumbers with a column called "num"

SELECT id, name, 
 PARSENAME(REPLACE(list1, ';', '.'), tblNumbers.num) AS L1,
 PARSENAME(REPLACE(list2, ';', '.'), tblNumbers.num) AS L2,
FROM YourDataTable
CROSS JOIN tblNumbers
ORDER BY id ASC, tblNumbers.num DESC

If the List columns ever do contain more than 4 elements each, you won't be able to use PARSENAME(), but you can still use the CROSS JOIN to a Numbers table approach, with a UDF you would have to write that would get the Nth element of a list. Pass the List column and the Num value to that function just like they're being passed to PARSENAME in my pseudo code above.



来源:https://stackoverflow.com/questions/56742851/join-together-multiple-columns-split-by-a-character-in-sql

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!