Query JSON data without node in SQL - sql

I have a column with JSON data in SQL server with the following values:
+----+-------------------------------------------------------------+
| ID | Values |
+----+-------------------------------------------------------------+
| 1 | [{"Name":"Test1","Type":null}] |
| 2 | [{"Name":"Test2","Type":null}] |
| 3 | [{"Name":"Test3","Type":null},{"Name":"Test4","Type":null}] |
| 4 | [{"Name":"Test5","Type":null},{"Name":"Test6","Type":null}] |
+----+-------------------------------------------------------------+
I want to query the above table in SQL and want results as:
+----+---------+
| ID | Values |
+----+---------+
| 1 | Test1 |
| 2 | Test2 |
| 3 | Test3 |
| 3 | Test4 |
| 4 | Test5 |
| 4 | Test6 |
+----+---------+

Since you are 2014, and IF you are open to a Table-Valued Function consider the following.
Tired of extracting strings, I modified a parse function to accept two non-like delimiters. In this case '"Name":"' and '"'
Example
Select A.ID
,[Values]=B.RetVal
From YourTable A
Cross Apply [dbo].[tvf-Str-Extract]([Values],'"Name":"','"') B
Returns
ID Values
1 Test1
2 Test2
3 Test3
3 Test4
4 Test5
4 Test6
The Function If Interested
CREATE FUNCTION [dbo].[tvf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)

If you cannot use SQL server 2016 or CLR functions and your json is fairly simple, you can translate it to xml and use SQL Server xml support
;with cte1(id, [values]) as (
select 1, '[{"Name":"Test1"}]' union all
select 2, '[{"Name":"Test2"}]' union all
select 3, '[{"Name":"Test3"},{"Name":"Test4"}]' union all
select 4, '[{"Name":"Test5"},{"Name":"Test6"}]'
), cte2 as (
select
id, cast(replace(replace(replace(replace([values],'[{"','<d '),'"}]','" />'),'"},{"','" />, <d '),'":','=') as xml) as [values]
from cte1
)
select
c.id,
t.c.value('#Name', 'nvarchar(128)') as [Values]
from cte2 as c
cross apply c.[values].nodes('d') as t(c)

Related

Sql Query Join on Comma Separated Value

I have a table that has a composite key and a comma separated value. I need the single row split into one row for each comma separated element. I have seen similar questions and similar answers but have not been able to translate them into a solution for myself.
I'm running SQL Server 2008 R2.
| Key Part 1 | Key Part 2 | Key Part 3 | Values |
|------------------------------------------------------|
| A | A | A | PDE,PPP,POR |
| A | A | B | PDE,XYZ |
| A | B | A | PDE,RRR |
|------------------------------------------------------|
and I need this as output
| Key Part 1 | Key Part 2 | Key Part 3 | Values | Sequence |
|-------------------------------------------------------------------|
| A | A | A | PDE | 0 |
| A | A | A | PPP | 1 |
| A | A | A | POR | 2 |
| A | A | B | PDE | 0 |
| A | A | B | XYZ | 1 |
| A | B | A | PDE | 0 |
| A | B | A | RRR | 1 |
|-------------------------------------------------------------------|
Thanks
Geoff
Here is a simple inline approach if you don't have or want a Split/Parse UDF
Example
Select A.[Key Part 1]
,A.[Key Part 2]
,A.[Key Part 3]
,B.*
From YourTable A
Cross Apply (
Select [Values] = LTrim(RTrim(X2.i.value('(./text())[1]', 'varchar(max)')))
,[Sequence] = Row_Number() over (Order By (Select null))-1
From (Select x = Cast('<x>' + replace(A.[Values],',','</x><x>')+'</x>' as xml)) X1
Cross Apply x.nodes('x') X2(i)
) B
Returns
EDIT - If Open to a Table-Valued Function
The Query would Look Like This
Select A.[Key Part 1]
,A.[Key Part 2]
,A.[Key Part 3]
,[Values] = B.RetVal
,[Sequence] = B.RetSeq-1
From #YourTable A
Cross Apply [dbo].[udf-Str-Parse-8K](A.[Values],',') B
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-8K]('John||Cappelletti||was||here','||')
If all CSV values are exactly 3 characters (as you have in your test data) you can use a a tally table in an incredibly efficient manner by creating the exact number of rows needed up front (as opposed to creating a row for every character to find the delimiter character)... because you already know the delimiter location.
In this case, I'll use a tally function but you can use a fixed tally table as well.
Code for the tfn_Tally function...
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE FUNCTION dbo.tfn_Tally
/* ============================================================================
07/20/2017 JL, Created. Capable of creating a sequense of rows
ranging from -10,000,000,000,000,000 to 10,000,000,000,000,000
============================================================================ */
(
#NumOfRows BIGINT,
#StartWith BIGINT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)), -- 10 rows
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b), -- 100 rows
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b), -- 10,000 rows
cte_n4 (n) AS (SELECT 1 FROM cte_n3 a CROSS JOIN cte_n3 b), -- 100,000,000 rows
cte_Tally (n) AS (
SELECT TOP (#NumOfRows)
(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) + #StartWith
FROM
cte_n4 a CROSS JOIN cte_n4 b -- 10,000,000,000,000,000 rows
)
SELECT
t.n
FROM
cte_Tally t;
GO
How to use it in the solution...
-- create some test data...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
KeyPart1 CHAR(1),
KeyPart2 CHAR(1),
KeyPart3 CHAR(1),
[Values] varchar(50)
);
INSERT #TestData (KeyPart1, KeyPart2, KeyPart3, [Values]) VALUES
('A', 'A', 'A', 'PDE,PPP,POR'),
('A', 'A', 'B', 'PDE,XYZ'),
('A', 'B', 'A', 'PDE,RRR,XXX,YYY,ZZZ,AAA,BBB,CCC');
--==========================================================
-- solution query...
SELECT
td.KeyPart1,
td.KeyPart2,
td.KeyPart3,
x.SplitValue,
[Sequence] = t.n
FROM
#TestData td
CROSS APPLY dbo.tfn_Tally(LEN(td.[Values]) - LEN(REPLACE(td.[Values], ',', '')) + 1, 0) t
CROSS APPLY ( VALUES (SUBSTRING(td.[Values], t.n * 4 + 1, 3)) ) x (SplitValue);
And the results...
KeyPart1 KeyPart2 KeyPart3 SplitValue Sequence
-------- -------- -------- ---------- --------------------
A A A PDE 0
A A A PPP 1
A A A POR 2
A A B PDE 0
A A B XYZ 1
A B A PDE 0
A B A RRR 1
A B A XXX 2
A B A YYY 3
A B A ZZZ 4
A B A AAA 5
A B A BBB 6
A B A CCC 7
If the assumption that all of the csv elements are the number of characters is incorrect, you'd be better off using a traditional tally based splitter. In which case my recommendation is DelimitedSplit8K written by Jeff Moden.
In that case, the solution query would look like this...
SELECT
td.KeyPart1,
td.KeyPart2,
td.KeyPart3,
SplitValue = dsk.Item,
[Sequence] = dsk.ItemNumber - 1
FROM
#TestData td
CROSS APPLY dbo.DelimitedSplit8K(td.[Values], ',') dsk;
Ann the result...
KeyPart1 KeyPart2 KeyPart3 SplitValue Sequence
-------- -------- -------- ---------- --------------------
A A A PDE 0
A A A PPP 1
A A A POR 2
A A B PDE 0
A A B XYZ 1
A B A PDE 0
A B A RRR 1
A B A XXX 2
A B A YYY 3
A B A ZZZ 4
A B A AAA 5
A B A BBB 6
A B A CCC 7
HTH, Jason
-- Create Table
Create table YourTable
(
p1 varchar(50),
p2 varchar(50),
p3 varchar(50),
pval varchar(50)
)
go
-- Insert Data
insert into YourTable values ('A','A','A','PDE,PPP,POR'),
('A','A','B','PDE,XYZ'),('A','B','A','PDE,RRR')
go
-- View Sample Data
SELECT p1, p2, p3 , pval FROM YourTable
go
-- Required Result
SELECT p1,p2,p3, LTRIM(RTRIM(Split.a.value('.', 'VARCHAR(100)'))) as Value1 , ROW_NUMBER() OVER(PARTITION BY id ORDER BY id ASC)-1 AS SequenceNo
FROM
(SELECT ROW_NUMBER() over (order by (SELECT NULL)) AS ID, p1,p2,p3, pval, CAST ('<M>' + REPLACE(pval, ',', '</M><M>') + '</M>' AS XML) AS Data from YourTable
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
go
-- Remove Temp created table
drop table YourTable
go

SQL splitfunction reverse

I am trying to make an SQL splitfunction which works reverse. Means
Input: 'Test1,Test2,Test3,Test4'
And the output should be:
| ID | Value |
| 1 | Test4 |
| 2 | Test3 |
| 3 | Test2 |
| 4 | Test1 |
I found a forwardworking function but I don't know what to change to make it work reversed.
I have tried some stuff but it doesn't work.
Here is the original
CREATE FUNCTION [dbo].[SplitString]
(
#InputString VARCHAR(8000),
#Delimiter CHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(StartPos,Endpos)
AS(
SELECT 0 AS StartPos, CHARINDEX(#Delimiter,#InputString) AS Endpos
UNION ALL
SELECT Endpos+1, CHARINDEX(#Delimiter,#InputString,Endpos+1)
FROM Split
WHERE Endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Value' = SUBSTRING(#InputString,StartPos
,COALESCE(NULLIF(Endpos,0)
,LEN(#InputString)+1)-StartPos)
FROM Split
)
GO
with
SELECT ID, Value
FROM dbo.SplitString('Test1,Test2,Test3,Test4', ',');
you will get the output.
Select * from [dbo].[udf-Str-Parse-8K-Rev]('Test1,Test2,Test3,Test4',',')
Returns
RetSeq RetVal
1 Test4
2 Test3
3 Test2
4 Test1
The UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-8K-Rev] (#String varchar(max),#Delimiter varchar(25))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N Desc)
,RetVal = LTrim(RTrim(Substring(#String, A.N, A.L)))
From cte4 A
);
--Orginal Source http://www.sqlservercentral.com/articles/Tally+Table/72993/
--Select * from [dbo].[udf-Str-Parse-8K-Rev]('Dog,Cat,House,Car',',')
Notice the Order By in the Final Select
you can it also do it with a query like this:
SELECT
cnt.n AS ID
, SUBSTRING_INDEX(SUBSTRING_INDEX('Test1,Test2,Test3,Test4', ',', cnt.n),',',-1) AS `VALUE` FROM
( SELECT 1 AS n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
) cnt
WHERE
cnt.n <= CHARACTER_LENGTH('Test1,Test2,Test3,Test4')
- CHARACTER_LENGTH(REPLACE('Test1,Test2,Test3,Test4',',',''))+1;
sample
MariaDB [(none)]> SELECT
-> cnt.n AS ID
-> , SUBSTRING_INDEX(SUBSTRING_INDEX('Test1,Test2,Test3,Test4', ',', cnt.n),',',-1) AS `VALUE` FROM
-> ( SELECT 1 AS n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
-> UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
-> ) cnt
-> WHERE
-> cnt.n <= CHARACTER_LENGTH('Test1,Test2,Test3,Test4')
-> - CHARACTER_LENGTH(REPLACE('Test1,Test2,Test3,Test4',',',''))+1;
+----+-------+
| ID | VALUE |
+----+-------+
| 1 | Test1 |
| 2 | Test2 |
| 3 | Test3 |
| 4 | Test4 |
+----+-------+
4 rows in set (0.00 sec)
MariaDB [(none)]>

Return Multiple Rows in One Cell

So I've looked around and seen the XML trick and the Variable trick, and neither really made enough sense to me to implement. What I have is a table with 4 Columns, The first is a unique identifier, the second is a relation to a different table, the third is varbinary(max), the last is a string. I want to combine columns three and four over column two. Is this possible?
Example of Data:
| FileId | UniqueI1 | BinaryData | FileName |
|---------+------------+--------------+----------|
| 1 | 1 | <byte> | asp.jpg |
| 2 | 1 | <byte> | asp1.jpg |
| 3 | 2 | <byte> | asp2.jpg |
| 4 | 2 | <byte> | asp3.jpg |
| 5 | 2 | <byte> | asp4.jpg |
Preferred Output:
| UniqueI1 | BinaryData | FileName |
|------------+------------------------------+------------------------------|
| 1 | <byte>, <byte> | asp.jpg, asp1.jpg |
| 2 | <byte>, <byte>, <byte> | asp2.jpg, asp3.jpg, asp4.jpg |
I appreciate any help you may be able to provide me.
Sounds like you're trying to group your data and aggregate the BinaryData and FileName columns by concatenating their values.
There are no built-in aggregates for concatenation in t-sql, but there are a couple of ways to reach the same results.
In my opinion, by far the easiest way is to write a custom aggregate in c# leveraging the CLR. But it can also be done using STUFF or XML. You should have a look at Does T-SQL have an aggregate function to concatenate strings?
Try this:
DECLARE #t TABLE
(
FileID INT ,
UniqueID INT ,
Data VARBINARY(100) ,
FileName VARCHAR(10)
)
INSERT INTO #t
VALUES ( 1, 1, 1, 'asp.jpg' ),
( 2, 1, 2, 'asp1.jpg' ),
( 3, 2, 3, 'asp2.jpg' ),
( 4, 2, 4, 'asp3.jpg' ),
( 5, 2, 5, 'asp4.jpg' )
SELECT UniqueID ,
MAX(ca.data) AS Data,
MAX(ca.name) AS Name
FROM #t t1
CROSS APPLY ( SELECT STUFF(
(SELECT ', ' + CONVERT(VARCHAR(MAX), t2.Data, 2)
FROM #t t2
WHERE t1.UniqueID = t2.UniqueID
ORDER BY FileID
FOR XML PATH('') ,
TYPE
).value('.', 'varchar(max)'), 1, 2, '') AS DATA ,
STUFF(
(SELECT ', ' + t2.FileName
FROM #t t2
WHERE t1.UniqueID = t2.UniqueID
ORDER BY FileID
FOR XML PATH('') ,
TYPE
).value('.', 'varchar(max)'), 1, 2, '') AS NAME
) ca
GROUP BY UniqueID
Output:
UniqueID Data Name
1 00000001, 00000002 asp.jpg, asp1.jpg
2 00000003, 00000004, 00000005 asp2.jpg, asp3.jpg, asp4.jpg
For pivoting:
WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( PARTITION BY UniqueID ORDER BY FileID ) AS rn
FROM #t
)
SELECT c.UniqueID ,
ca1.[1] AS Data1 ,
ca1.[2] AS Data2 ,
ca1.[3] AS Data3 ,
ca2.[1] AS File1 ,
ca2.[2] AS File2 ,
ca2.[3] AS File3
FROM cte c
CROSS APPLY ( SELECT *
FROM ( SELECT UniqueID ,
rn ,
Data
FROM cte ci
WHERE ci.UniqueID = c.UniqueID
) t PIVOT( MAX(Data) FOR rn IN ( [1], [2], [3] ) ) p
) ca1
CROSS APPLY ( SELECT *
FROM ( SELECT UniqueID ,
rn ,
FileName
FROM cte ci
WHERE ci.UniqueID = c.UniqueID
) t PIVOT( MAX(FileName) FOR rn IN ( [1], [2], [3] ) ) p
) ca2
GROUP BY c.UniqueID, ca1.[1], ca1.[2], ca1.[3], ca2.[1], ca2.[2], ca2.[3]
Output:
UniqueID Data1 Data2 Data3 File1 File2 File3
1 0x00000001 0x00000002 NULL asp.jpg asp1.jpg NULL
2 0x00000003 0x00000004 0x00000005 asp2.jpg asp3.jpg asp4.jpg
You can change this to dynamic query if you don't want to manually add additional files.

How to comapre two columns of a table in sql?

In a table there are two columns:
-----------
| A | B |
-----------
| 1 | 5 |
| 2 | 1 |
| 3 | 2 |
| 4 | 1 |
-----------
Want a table where if A=B then
-------------------
|Match | notMatch|
-------------------
| 1 | 5 |
| 2 | 3 |
| Null | 4 |
-------------------
How can i do this?
I tried something which shows the Matched part
select distinct C.A as A from Table c inner join Table d on c.A=d.B
Try this:
;WITH TempTable(A, B) AS(
SELECT 1, 5 UNION ALL
SELECT 2, 1 UNION ALL
SELECT 3, 2 UNION ALL
SELECT 4, 1
)
,CTE(Val) AS(
SELECT A FROM TempTable UNION ALL
SELECT B FROM TempTable
)
,Match AS(
SELECT
Rn = ROW_NUMBER() OVER(ORDER BY Val),
Val
FROM CTE c
GROUP BY Val
HAVING COUNT(Val) > 1
)
,NotMatch AS(
SELECT
Rn = ROW_NUMBER() OVER(ORDER BY Val),
Val
FROM CTE c
GROUP BY Val
HAVING COUNT(Val) = 1
)
SELECT
Match = m.Val,
NotMatch= n.Val
FROM Match m
FULL JOIN NotMatch n
ON n.Rn = m.Rn
Try with EXCEPT, MINUS and INTERSECT Statements.
like this:
SELECT A FROM TABLE1 INTERSECT SELECT B FROM TABLE1;
You might want this:
SELECT DISTINCT
C.A as A
FROM
Table c
LEFT OUTER JOIN
Table d
ON
c.A=d.B
WHERE
d.ID IS NULL
Please Note that I use d.ID as an example because I don't see your schema. An alternate is to explicitly state all d.columns IS NULL in WHERE clause.
Your requirement is kind of - let's call it - interesting. Here is a way to solve it using pivot. Personally I would have chosen a different table structure and another way to select data:
Test data:
DECLARE #t table(A TINYINT, B TINYINT)
INSERT #t values
(1,5),(2,1),
(3,2),(4,1)
Query:
;WITH B AS
(
( SELECT A FROM #t
EXCEPT
SELECT B FROM #t)
UNION ALL
( SELECT B FROM #t
EXCEPT
SELECT A FROM #t)
), A AS
(
SELECT A val
FROM #t
INTERSECT
SELECT B
FROM #t
), combine as
(
SELECT val, 'A' col, row_number() over (order by (select 1)) rn FROM A
UNION ALL
SELECT A, 'B' col, row_number() over (order by (select 1)) rn
FROM B
)
SELECT [A], [B]
FROM combine
PIVOT (MAX(val) FOR [col] IN ([A], [B])) AS pvt
Result:
A B
1 3
2 4
NULL 5

tSQL UNPIVOT of comma concatenated column into multiple rows

I have a table that has a value column. The value could be one value or it could be multiple values separated with a comma:
id | assess_id | question_key | item_value
---+-----------+--------------+-----------
1 | 859 | Cust_A_1 | 1,5
2 | 859 | Cust_B_1 | 2
I need to unpivot the data based on the item_value to look like this:
id | assess_id | question_key | item_value
---+-----------+--------------+-----------
1 | 859 | Cust_A_1 | 1
1 | 859 | Cust_A_1 | 5
2 | 859 | Cust_B_1 | 2
How does one do that in tSQL on SQL Server 2012?
We have a user defined function that we use for stuff like this that we called "split_delimiter":
CREATE FUNCTION [dbo].[split_delimiter](#delimited_string VARCHAR(8000), #delimiter_type CHAR(1))
RETURNS TABLE AS
RETURN
WITH cte10(num) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,cte100(num) AS
(
SELECT 1
FROM cte10 t1, cte10 t2
)
,cte10000(num) AS
(
SELECT 1
FROM cte100 t1, cte100 t2
)
,cte1(num) AS
(
SELECT TOP (ISNULL(DATALENGTH(#delimited_string),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM cte10000
)
,cte2(num) AS
(
SELECT 1
UNION ALL
SELECT t.num+1
FROM cte1 t
WHERE SUBSTRING(#delimited_string,t.num,1) = #delimiter_type
)
,cte3(num,[len]) AS
(
SELECT t.num
,ISNULL(NULLIF(CHARINDEX(#delimiter_type,#delimited_string,t.num),0)-t.num,8000)
FROM cte2 t
)
SELECT delimited_item_num = ROW_NUMBER() OVER(ORDER BY t.num)
,delimited_value = SUBSTRING(#delimited_string, t.num, t.[len])
FROM cte3 t;
GO
It will take a varchar value up to 8000 characters and will return a table with the delimited elements broken into rows. In your example, you'll want to use an outer apply to turn those delimited values into separate rows:
SELECT my_table.id, my_table.assess_id, question_key, my_table.delimited_items.item_value
FROM my_table
OUTER APPLY(
SELECT delimited_value AS item_value
FROM my_database.dbo.split_delimiter(my_table.item_value, ',')
) AS delimited_items