Splitting delimited values across multiple columns on same row.

  • Hi All,

    Not sure If I'm over complicating the process but I'm struggling to write or find a solution on line that works exactly how I expect to see the results.

    I have found one solution but there are limitations on the PARSENAME when over 4 values.

    Below is just copy pasted a solution from the web, I know this is overkill with the replace etc... I just haven't modified the code to clean that up.

    I've tried alternatives using string split, which wasn't successful and the closest I can find it the above.

    Below is the sample code I'm trying to work with. The string I have has a lot more | values than 4-5 but I guess that shouldn't make a difference if I can get 5-6 values working (unless there is a limitation over X amount like PARSENAME has)

    I'm thinking CHARINDEX is the way to go, but I can't get this working.

    Any help would be appreciated.

    -- WORKING EXAMPLE 

    DROP TABLE IF exists #PipeStringTemp

    CREATE TABLE #PipeStringTemp(PipeValues VARCHAR(1000));

    INSERT INTO #PipeStringTemp(PipeValues)
    VALUES ('11/10/2023|5|5|5')

    SELECT
    REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 1)) AS ColA
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 2)) AS ColB
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 3)) AS ColC
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 4)) AS ColD
    FROM
    #PipeStringTemp;


    GO

    -- LIMITATION OF PARSENAME

    DROP TABLE IF exists #PipeStringTemp2

    CREATE TABLE #PipeStringTemp2(PipeValues VARCHAR(1000));

    INSERT INTO #PipeStringTemp2(PipeValues)
    VALUES ('11/10/2023|5|5|5|2|1')

    SELECT
    REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 1)) AS ColA
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 2)) AS ColB
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 3)) AS ColC
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 4)) AS ColD
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 5)) AS ColE
    ,REVERSE(PARSENAME(REPLACE(REVERSE(PipeValues), '|', '.'), 6)) AS ColF
    FROM
    #PipeStringTemp2;
  • PARSENAME won't work for more than 4 "parts".

    and instead of trying to invent the wheel again look at already made functions

    https://www.sqlservercentral.com/articles/reaping-the-benefits-of-the-window-functions-in-t-sql-2 -- use this one as it is faster than the original on SQL Versions that allow for LEAD window function

    and its original https://www.sqlservercentral.com/articles/tally-oh-an-improved-sql-8k-%e2%80%9ccsv-splitter%e2%80%9d-function

    read both as they are rather interesting reads.

    from your use of "reverse" it seems that you wish for the elements to be returned in reverse order - why?

    and if this is the case how does it work for rows with different number of elements - where the "date" won't always be on the same output column.

  • frederico_fonseca wrote:

    from your use of "reverse" it seems that you wish for the elements to be returned in reverse order - why?

    and if this is the case how does it work for rows with different number of elements - where the "date" won't always be on the same output column.

    In terms of the reverse, there is no requirement to it, as mentioned it was just code taken online. Just to show what I'm trying to achieve with the output. Probably confused the situation by having it in.

    The good news is, it's a fixed element per type coming from the Application. for one of my types e.g. Customers there will be a FirstName, LastName, Phone, DOB. If details are entered or not, it will always be John|||2023-10-11

    So I can safely assume  as this is a one of export of Data, If the app Changes then correct, I will need to make that change but its very unlikely.

    I'll review those articles as I got an example  working on 1 row but when there is multiple rows Its not.

     

     

     

     

  • I'm still struggling to adapt the articles based on the examples.

    Maybe I simplified it easier on the previous example.

    I've mocked up a more "realistic" data set but still cannot get it as expected.

     DROP TABLE IF EXISTS #data

     CREATE TABLE #data (ResourceType VARCHAR(255), CustomerID INT, DOB DATE, details VARCHAR(1000))

        INSERT INTO #data (ResourceType,CustomerID,DOB,details)
        VALUES
        ('records',1,'2023-10-11','11/10/2023|5|5|5|2|TEST 123'),
        ('records',1,'2023-10-11','11/10/2023|2|1|1|1|'),
        ('records',1,'2023-10-11','11/10/2023|1|||3|')

        SELECT * FROM #data

    I was able to get it working using the below but I  Need to keep putting a crazy amount of statements which will fail if result set keeps growing.

     

    MAX(CASE WHEN RowNum = 1) THEN  Value END

    MAX(CASE WHEN RowNum = 2) THEN  Value END

    MAX(CASE WHEN RowNum = n. ..) THEN  Value END

     

    Do you have any further assistance.

  • yes you will need to either change the code and have multiple conditions or use dynamic sql to build the output if your final output should have each "string" placed on its own column.

    pretty much similar to what you were trying to do with parsename.

  • Does this help? It relies on Jeff Moden's infamous splitter which, given the number of points you have, I imagine you've heard of.

    DROP TABLE IF EXISTS #PipeStringTemp;

    CREATE TABLE #PipeStringTemp
    (
    Id INT IDENTITY(1, 1)
    ,PipeValues VARCHAR(1000)
    );

    INSERT INTO #PipeStringTemp
    (
    PipeValues
    )
    VALUES
    ('11/10/2023|5|5|5|2|TEST 123')
    ,('11/10/2023|2|1|1|1|')
    ,('11/10/2023|1|||3|');

    WITH detail
    AS (SELECT pst.Id
    ,pst.PipeValues
    ,c1.ItemNumber
    ,c1.Item
    FROM #PipeStringTemp pst
    CROSS APPLY
    (SELECT * FROM dbo.DelimitedSplit8K (pst.PipeValues, '|') ) c1 )
    SELECT detail.Id
    ,detail.PipeValues
    ,Somedate = MAX (IIF(detail.ItemNumber = 1, detail.Item, NULL))
    ,Int1 = MAX (IIF(detail.ItemNumber = 2, detail.Item, NULL))
    ,Int2 = MAX (IIF(detail.ItemNumber = 3, detail.Item, NULL))
    --etc etc
    FROM detail
    GROUP BY detail.Id
    ,detail.PipeValues;

    The absence of evidence is not evidence of absence
    - Martin Rees
    The absence of consumable DDL, sample data and desired results is, however, evidence of the absence of my response
    - Phil Parkin

  • have a look at this example - based on something I do for dynamic reports

     

    drop table if exists #data

     CREATE TABLE #data
    ( RecordID int identity(1, 1) -- added as the use of pivot for this purposes requires a UNIQUE set of columns and sample data didn't contain it
    , ResourceType VARCHAR(255)
    , CustomerID INT
    , DOB DATE
    , details VARCHAR(1000)
    )

    insert into #data
    ( ResourceType
    , CustomerID
    , DOB
    , details
    )
    values ('records', 1, '2023-10-11', '11/10/2023|5|5|5|2|TEST 123')
    , ('records', 1, '2023-10-11', '11/10/2023|2|1|1|1|')
    , ('records', 1, '2023-10-11', '11/10/2023|1|||3|')

    drop table if exists #workdata;

    select RecordID
    , ResourceType
    , CustomerID
    , DOB
    , 'FLD' + convert(varchar(10), ItemNumber) as FieldID -- add a prefix so we can have a "better" output column names
    , Item
    into #workdata
    from #data dt
    cross apply dbo.DelimitedSplit8K_LEAD(dt.details, '|')

    select *
    from #workdata

    declare @execsql nvarchar(max)
    declare @basesql nvarchar(max)
    declare @pivcols nvarchar(max)
    declare @pivsel nvarchar(max)

    -- get list of columns to pivot as well as select list
    -- optionally add a coalesce to replace nulls with spaces (or zeros/specific value if we know what a particular fieldid datatype and default value is)
    select @pivcols = string_agg(convert(nvarchar(max), quotename(fieldID)), ',') within group (order by fieldid)
    , @pivsel = string_agg(convert(nvarchar(max), ', piv.' + quotename(fieldID)) + ' as ' + quotename(fieldID), '') within group (order by fieldid)

    from (select distinct fieldid
    from #workdata
    ) t


    set @basesql = '
    select piv.RecordID
    , piv.ResourceType
    , piv.CustomerID
    , piv.DOB
    --pivsel
    from ( -- subselect added so we can remove unwanted columns and to make block of code common to any further blocks required
    select *
    from #workdata
    ) src
    PIVOT (max(src.item) for fieldid in
    (
    --pivcols
    )
    ) piv
    order by piv.RecordID
    '
    set @execsql = replace(replace(@basesql, '--pivsel', @pivsel), '--pivcols', @pivcols)

    print @execsql

    exec sys.sp_executesql @execsql
  • It's just another expression per value, not statement.  You could pre-code 10 or even 20 values without a lot of trouble:

    SELECT ResourceType, CustomerID, DOB,
    ca1.*
    FROM #data
    CROSS APPLY (
    SELECT
    MAX(CASE WHEN ItemNumber = 1 THEN Item END) AS Value01,
    MAX(CASE WHEN ItemNumber = 2 THEN Item END) AS Value02,
    MAX(CASE WHEN ItemNumber = 3 THEN Item END) AS Value03,
    MAX(CASE WHEN ItemNumber = 4 THEN Item END) AS Value04,
    MAX(CASE WHEN ItemNumber = 5 THEN Item END) AS Value05,
    MAX(CASE WHEN ItemNumber = 6 THEN Item END) AS Value06,
    MAX(CASE WHEN ItemNumber = 7 THEN Item END) AS Value07,
    MAX(CASE WHEN ItemNumber = 8 THEN Item END) AS Value08,
    MAX(CASE WHEN ItemNumber = 9 THEN Item END) AS Value09,
    MAX(CASE WHEN ItemNumber =10 THEN Item END) AS Value10 /*, ...*/
    FROM (
    SELECT *
    FROM dbo.DelimitedSplit8K(details, '|') ds
    ) AS derived
    ) AS ca1
    /*ORDER BY ...*/

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • Hi All,

    Sorry for the delay. I was able to get this working using the help of the above. I did use a custom string split function which I found online.

    I initially got it working using the STRING_SPLIT function, however i found out it didn't give me the correct ordinality, it kept changing which caused issues.

    So using the custom string split scripts with a CROSS APPLY got it to work for me.

    Since the values in Pipe delimiter were fixed per type, I used the MAX(CASE ...) option  which worked.

    The code was very similar to Scotts above.

    Thanks for every ones help.

     

     

Viewing 9 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic. Login to reply