SQL Server 如何强制SQL将“- -“视为单个分隔符?

oug3syen  于 2022-12-10  发布在  其他
关注(0)|答案(3)|浏览(118)

I need to delimit @uid by "-". The issue is my data set has "--1" and I need it be treated as "-1"
I need @uid = '1585-1586--1-5417-2347-8865' to output this:

Instead of:

How can I achieve this in SQL?

vaj7vani

vaj7vani1#

The answer you have helps you a little here, however, with no definition of [dbo].[fnSplit] doesn't help any one else.
If we can assume that the data is well defined (has 6 columns), then we could "spam" some CHARINDEX functions to do this. You will, as shown in the answer, need to replace all the delimiters and then reinsert the value of - for the double delimiter:

DECLARE @UID varchar(30) = '1585-1586--1-5417-2347-8865';

DECLARE @Delimiter char(1) = '-';

SELECT SUBSTRING(ca.FixedUID,1,C1.I-1) AS Col1,
       SUBSTRING(ca.FixedUID,C1.I+1, C2.I-C1.I-1) AS Col2,
       SUBSTRING(ca.FixedUID,C2.I+1, C3.I-C2.I-1) AS Col3,
       SUBSTRING(ca.FixedUID,C3.I+1, C4.I-C3.I-1) AS Col4,
       SUBSTRING(ca.FixedUID,C4.I+1, C5.I-C4.I-1) AS Col5,
       SUBSTRING(ca.FixedUID,C5.I+1, LEN(ca.FixedUID)-C5.I) AS Col6
FROM (VALUES(@UID))V([UID])
     CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,@Delimiter,'|'),'||','|' + @Delimiter)))ca(FixedUID)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID)))C1(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C1.I+1)))C2(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C2.I+1)))C3(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C3.I+1)))C4(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C4.I+1)))C5(I);

Of course, if you had a "empty" value, then this will fail:

DECLARE @UID varchar(30) = '1585--71-5417-2347-8865';

DECLARE @Delimiter char(1) = '-';

SELECT SUBSTRING(ca.FixedUID,1,C1.I-1) AS Col1,
       SUBSTRING(ca.FixedUID,C1.I+1, C2.I-C1.I-1) AS Col2,
       SUBSTRING(ca.FixedUID,C2.I+1, C3.I-C2.I-1) AS Col3,
       SUBSTRING(ca.FixedUID,C3.I+1, C4.I-C3.I-1) AS Col4,
       SUBSTRING(ca.FixedUID,C4.I+1, C5.I-C4.I-1) AS Col5,
       SUBSTRING(ca.FixedUID,C5.I+1, LEN(ca.FixedUID)-C5.I) AS Col6
FROM (VALUES(@UID))V([UID])
     CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,@Delimiter,'|'),'||','|' + @Delimiter)))ca(FixedUID)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID)))C1(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C1.I+1)))C2(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C2.I+1)))C3(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C3.I+1)))C4(I)
     CROSS APPLY (VALUES(CHARINDEX('|',ca.FixedUID,C4.I+1)))C5(I);

Invalid length parameter passed to the LEFT or SUBSTRING function.
And hence why a delimiter than can appear in your data should never be used (however, one can hope that as these all appear to be integer values then a NULL value wouldn't exist and there would be a 0 instead: '1585-0-71-5417-2347-8865' ).
If you used a string splitter like DelimitedSpluit8K_LEAD then you could Pivot (and unpivot) the data fine, but the values would be in the wrong positions with the above example:

SELECT MAX(CASE DS.ItemNumber WHEN 1 THEN DS.Item END) AS Col1,
       MAX(CASE DS.ItemNumber WHEN 2 THEN DS.Item END) AS Col2,
       MAX(CASE DS.ItemNumber WHEN 3 THEN DS.Item END) AS Col3,
       MAX(CASE DS.ItemNumber WHEN 4 THEN DS.Item END) AS Col4,
       MAX(CASE DS.ItemNumber WHEN 5 THEN DS.Item END) AS Col5,
       MAX(CASE DS.ItemNumber WHEN 6 THEN DS.Item END) AS Col6
FROM (VALUES(@UID))V([UID])
     CROSS APPLY (VALUES(REPLACE(REPLACE(V.UID,@Delimiter,'|'),'||','|' + @Delimiter)))ca(FixedUID)
     CROSS APPLY dbo.DelimitedSplit8K_LEAD(ca.FixedUID,'|') DS;

Which will result in the below:

Col1 Col2 Col3 Col4 Col5 Col6
---- ---- ---- ---- ---- ----
1585 -71  5417 2347 8865 NULL
mrzz3bfm

mrzz3bfm2#

Basically what I'm doing is a recursive cte from 6 to 1. each iteration I am removing the last delimited number and moving it to col_val column. I decided to use reverse so that I could then use patindex to find the hyphen then the number. Doing that made it possible to get the negative values. In reverse the string looks like 1--6851-5851-0 then patindex('%-[0-9]%', <string>) returns 2 and because I used right function of the string 0-1585-1586--1 it will return -1
I added '0-' to the beginning of the delim_column because I want to use patindex without having to account for the last delimited column.
The column col_val is repeating all the above but instead of using @uid it is using delim_column
Here is what each iteration looks like:

col_num     delim_column                col_val     loc
6           0-1585-1586--1-5417-2347    8865        4
5           0-1585-1586--1-5417         2347        4
4           0-1585-1586--1              5417        4
3           0-1585-1586                 -1          2
2           0-1585                      1586        4
1           0                           1585        4

Then I'm pivoting the columns using a simple choose function. That will make the column names clean.

DECLARE
    @uid VARCHAR(MAX) = '1585-1586--156-5417-2347-8865',
    @delim_count INT = 0

--First, count the number of delimiters. We do this by temporarily replacing '--' with a single '-'
--and then count the difference in lengths of the two strings (one with '-' and one without)

SELECT @delim_count = LEN(REPLACE(@uid, '--', '-')) - LEN(REPLACE(REPLACE(@uid, '--', '-'), '-','')) - IIF(@uid LIKE '-%', 1, 0)

--next a recursive cte that will lop off the last number each iteration and move the last value to col_val
;WITH fnsplit(col_num, delim_column, col_val, loc)
AS
(
    SELECT 
        @delim_count+1    --start with 6 and then go to 1. remove the +1 and replace col_num > 0 for a zero index
        ,'0-'+SUBSTRING(@uid,0, LEN(@uid) - LEN(RIGHT(@uid, PATINDEX('%-[0-9]%', reverse(@uid)) - 1)) )
        ,RIGHT(@uid, PATINDEX('%-[0-9]%', REVERSE(@uid)) - 1)
        ,PATINDEX('%-[0-9]%', REVERSE(@uid)) - 1
    UNION ALL
    SELECT 
         col_num - 1
        ,SUBSTRING(delim_column,0, LEN(delim_column) - LEN(RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)) )
        ,RIGHT(delim_column, PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1)
        ,PATINDEX('%-[0-9]%', REVERSE(delim_column)) - 1
    FROM 
        fnsplit
    WHERE 
        col_num > 1
)

--select * from fnsplit   -- uncomment here and comment all below to see the recursion 

SELECT 
    *
FROM
(
    SELECT 
         column_name
        ,col_val
    FROM
        fnsplit
    CROSS APPLY
        (SELECT CHOOSE(col_num, 'Col_A','Col_B','Col_C', 'Col_D', 'Col_E', 'Col_F')) tbl(column_name)
)PVT
    PIVOT
    (
        MAX(col_val)
            FOR column_name IN ([Col_A], [Col_B], [Col_C], [Col_D], [Col_E], [Col_F])
    ) PVT1
shyt4zoc

shyt4zoc3#

使用下面的脚本可以获得所需的结果。该脚本依赖于名为[fnSplit]的用户定义函数。[fnSplit] UDF将在后面的文章中定义。
第一个

相关问题