How to split a string (with delimiter) using a single line query in SQL without the usage of user defined function?

StackOverflow https://stackoverflow.com/questions/22367314

Question

Scenario


The table structure of E_STProcedure

According to my existing scenario as shown in the above table STProcedure there could exists combination groups with respect to individual STProcedure. In the above picture it can be seen that the following relationships are true with STProcedureID and GroupID

  1. A => A_B_C
  2. B => B_A_E
  3. C =>
  4. D => D_B_C_E
  5. E => D_E_A

Requirement + Initial Solution


It is required that the GroupIDs should be split with the delimiter '_' in a single line query.
I was able to find a solution by using a user defined function as follows

  • User defined function - fnSplit
ALTER FUNCTION  E1.fnSplit (@sInputList udtStringMax, @sDelimiter udtStringMax = '_') 
                                RETURNS @List TABLE (item udtStringMax,CNT INT IDENTITY(1,1))

BEGIN
    DECLARE @sItem udtStringMax
    WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
    BEGIN
        SELECT
            @sItem=SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1),
            @sInputList=SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))

        IF LEN(@sItem) > 0
            INSERT INTO @List SELECT @sItem
    END

    IF LEN(@sInputList) > 0
        INSERT INTO @List SELECT @sInputList -- Put the last item in
    RETURN
END
  • Single line query was as follows which used fnSplit
SELECT * FROM E1.E_STProcedure WHERE STProcedureID  IN  
    (SELECT item FROM E1.fnSplit((SELECT GroupID FROM E1.E_STProcedure WHERE STProcedureID='||OWNER||'),'_'))

where ||OWNER|| is the individual STProcedureID and E1 is the schema of the DB that I had

Limitations


Since the system was implemented in an MTMS (Multi-Tenant Multi-Schema) environment each query was schema dependant and had more than 100 schemas, the above code would only work for E1 schema.

I came to a conclusion that maybe there exists a workaround to split a string (with delimiter) using a single line query in SQL without the usage of user defined function, because the only other option is to replace and execute the query for each schema with its token.

Was it helpful?

Solution 2

I was able to formulate the following solution with the help of my colleague

SELECT * FROM E1.E_STProcedure WHERE STProcedureID  IN  
    (SELECT ST2.STProcedureID   FROM E1.E_STProcedure ST1 
                                JOIN E1.E_STProcedure ST2 ON ST1.GroupID LIKE '%'+ST2.STProcedureID+'%' 
                                WHERE ST1.STProcedureID ='||OWNER||')

The Final Result


Explanation

WHERE clause

What happens here is that the WHERE clause will definitely cut down the result to a single record from ST1 relative to the respective GUID ||OWNER||
Lets simplify with the following example

WHERE ST1.STProcedureID ='17bfa492-8464-423f-a5f4-32978186e112'

The result of the WHERE clause

JOIN clause

The JOIN statement brings out the juice, The single entry from ST1.GroupID is checked for its containing sub-string from ST2.STProcedureID as shown below

E1.E_STProcedure ST1 
JOIN 
E1.E_STProcedure ST2 
ON 
ST1.GroupID LIKE '%'+ST2.STProcedureID+'%'

The result of the JOIN clause


I hope the code is pretty readable for you all.

OTHER TIPS

Alright, for the sake of argument, lets skip the CTE for now and instead do "long and ugly" with some xml. I'll leave your one line break in here.The basics of this are pulled from Brad Shulz by way of Aaron Bertrand. I changed from an int list, shortened a bit, took it out of a function, and put it on the one line.

SELECT * FROM E_STProcedure WHERE STProcedureID  IN  
    (SELECT Item FROM (SELECT Item = b.i.value('(./text())[1]', 'VARCHAR(MAX)') FROM (SELECT x = CONVERT(XML, '<i>' + REPLACE(Group_ID, '_', '</i><i>') + '</i>').query('.') FROM E_STProcedure WHERE STProcedureID='||OWNER||') a CROSS APPLY x.nodes('i') b(i)) c WHERE Item IS NOT NULL)

Given a few line breaks it reads better:

SELECT * 
  FROM E_STProcedure 
 WHERE STProcedureID IN (
       SELECT Item 
       FROM (SELECT Item = b.i.value('(./text())[1]', 'VARCHAR(MAX)') 
               FROM (SELECT x = CONVERT(XML, '<i>' + 
                                              REPLACE(Group_ID, '_', '</i><i>') +
                                             '</i>').query('.') 
                       FROM E_STProcedure 
                      WHERE STProcedureID='||OWNER||') a 
                    CROSS APPLY 
                    x.nodes('i') b(i)
                    ) c 
               WHERE Item IS NOT NULL)

Still could be done as a CTE, but I don't see a gain.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top