【问题标题】:T-SQL split string based on delimiter基于分隔符的 T-SQL 拆分字符串
【发布时间】:2014-03-13 03:54:05
【问题描述】:

我有一些数据想根据可能存在或不存在的分隔符进行拆分。

示例数据:

John/Smith
Jane/Doe
Steve
Bob/Johnson

我正在使用以下代码将此数据拆分为名字和姓氏:

SELECT SUBSTRING(myColumn, 1, CHARINDEX('/', myColumn)-1) AS FirstName,
       SUBSTRING(myColumn, CHARINDEX('/', myColumn) + 1, 1000) AS LastName
FROM   MyTable

我想要的结果:

FirstName---LastName
John--------Smith
Jane--------Doe
Steve-------NULL
Bob---------Johnson

只要所有行都具有预期的分隔符,此代码就可以正常工作,但是当行没有时会出错:

"Invalid length parameter passed to the LEFT or SUBSTRING function."

如何重新编写它才能正常工作?

【问题讨论】:

    标签: sql sql-server sql-server-2008 tsql sql-server-2008-r2


    【解决方案1】:

    也许这会对你有所帮助。

    SELECT SUBSTRING(myColumn, 1, CASE CHARINDEX('/', myColumn)
                WHEN 0
                    THEN LEN(myColumn)
                ELSE CHARINDEX('/', myColumn) - 1
                END) AS FirstName
        ,SUBSTRING(myColumn, CASE CHARINDEX('/', myColumn)
                WHEN 0
                    THEN LEN(myColumn) + 1
                ELSE CHARINDEX('/', myColumn) + 1
                END, 1000) AS LastName
    FROM MyTable
    

    【讨论】:

      【解决方案2】:

      对于那些寻找 SQL Server 2016+ 答案的人。使用内置的 STRING_SPLIT 函数

      例如:

      DECLARE @tags NVARCHAR(400) = 'clothing,road,,touring,bike'  
      
      SELECT value  
      FROM STRING_SPLIT(@tags, ',')  
      WHERE RTRIM(value) <> '';  
      

      参考:https://msdn.microsoft.com/en-nz/library/mt684588.aspx

      【讨论】:

      • 我不确定这是否能回答问题,也不确定它是否像上面的 CASE 语句方法那样有用。 STRING_SPLIT 函数将其转换为连接的表。那么如何获得第一个位置“名字”第二个“姓氏”?使用 Cross-Apply 将我的 ds 转换为多行。这是将字符串数据转换为表格的好方法。像 OP 一样,我正在尝试将单列转换为两列。 STRING_SPLIT 将它变成两行,无法分辨哪一行是“第一”“第二”“第三”(除非 Over 工作)。但这变得更加复杂。
      【解决方案3】:

      尝试过滤掉包含带有分隔符的字符串的行,然后只处理那些:

      SELECT SUBSTRING(myColumn, 1, CHARINDEX('/', myColumn)-1) AS FirstName,
             SUBSTRING(myColumn, CHARINDEX('/', myColumn) + 1, 1000) AS LastName
      FROM   MyTable
      WHERE CHARINDEX('/', myColumn) > 0
      

      或者

      SELECT SUBSTRING(myColumn, 1, CHARINDEX('/', myColumn)-1) AS FirstName,
             SUBSTRING(myColumn, CHARINDEX('/', myColumn) + 1, 1000) AS LastName
      FROM   MyTable
      WHERE myColumn LIKE '%/%'
      

      【讨论】:

        【解决方案4】:
        选择案例 当 CHARINDEX('/', myColumn, 0) = 0 然后我的专栏 ELSE LEFT(myColumn, CHARINDEX('/', myColumn, 0)-1) END AS 名字 ,案子 当 CHARINDEX('/', myColumn, 0) = 0 然后 '' ELSE RIGHT(myColumn, CHARINDEX('/', REVERSE(myColumn), 0)-1) END AS 姓氏 从我的表

        【讨论】:

          【解决方案5】:
          ALTER FUNCTION [dbo].[split_string](
                    @delimited NVARCHAR(MAX),
                    @delimiter NVARCHAR(100)
                  ) RETURNS @t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
          AS
          BEGIN
            DECLARE @xml XML
            SET @xml = N'<t>' + REPLACE(@delimited,@delimiter,'</t><t>') + '</t>'
          
            INSERT INTO @t(val)
            SELECT  r.value('.','varchar(MAX)') as item
            FROM  @xml.nodes('/t') as records(r)
            RETURN
          END
          

          【讨论】:

            【解决方案6】:

            我只是想提供一种替代方法来拆分具有多个分隔符的字符串,以防您使用 2016 年以下的 SQL Server 版本。

            一般的思路是将字符串中的所有字符拆分出来,确定分隔符的位置,然后得到相对于分隔符的子字符串。这是一个示例:

            -- Sample data
            DECLARE @testTable TABLE (
                TestString      VARCHAR(50)
            )
            INSERT INTO @testTable VALUES 
                ('Teststring,1,2,3')
                ,('Test')
            
            DECLARE @delimiter VARCHAR(1) = ','
            
            -- Generate numbers with which we can enumerate
            ;WITH Numbers AS (
                SELECT 1 AS N
            
                UNION ALL 
            
                SELECT N + 1
                FROM Numbers 
                WHERE N < 255
            ), 
            -- Enumerate letters in the string and select only the delimiters
            Letters AS (
                SELECT  n.N
                        , SUBSTRING(t.TestString, n.N, 1) AS Letter
                        , t.TestString 
                        , ROW_NUMBER() OVER (   PARTITION BY t.TestString
                                                ORDER BY n.N
                                            ) AS Delimiter_Number 
                FROM Numbers n
                    INNER JOIN @testTable t
                        ON n <= LEN(t.TestString)
                WHERE SUBSTRING(t.TestString, n, 1) = @delimiter 
            
                UNION 
            
                -- Include 0th position to "delimit" the start of the string
                SELECT  0
                        , NULL
                        , t.TestString 
                        , 0
                FROM @testTable t 
            )
            -- Obtain substrings based on delimiter positions
            SELECT  t.TestString 
                    , ds.Delimiter_Number + 1 AS Position
                    , SUBSTRING(t.TestString, ds.N + 1, ISNULL(de.N, LEN(t.TestString) + 1) - ds.N - 1) AS Delimited_Substring 
            FROM @testTable t
                LEFT JOIN Letters ds
                    ON t.TestString = ds.TestString 
                LEFT JOIN Letters de
                    ON t.TestString = de.TestString 
                    AND ds.Delimiter_Number + 1 = de.Delimiter_Number  
            OPTION (MAXRECURSION 0)
            

            【讨论】:

              【解决方案7】:

              当只有一个分隔符时,上面的示例可以正常工作,但对于多个分隔符就不能很好地扩展。请注意,这仅适用于 SQL Server 2016 及更高版本。

              /*Some Sample Data*/
              DECLARE @mytable TABLE ([id] VARCHAR(10), [name] VARCHAR(1000));
              INSERT INTO @mytable
              VALUES ('1','John/Smith'),('2','Jane/Doe'), ('3','Steve'), ('4','Bob/Johnson')
              
              
              /*Split based on delimeter*/
              SELECT P.id, [1] 'FirstName', [2] 'LastName', [3] 'Col3', [4] 'Col4'
              FROM(
                  SELECT A.id, X1.VALUE, ROW_NUMBER() OVER (PARTITION BY A.id ORDER BY A.id) RN
                  FROM @mytable A
                  CROSS APPLY STRING_SPLIT(A.name, '/') X1
                  ) A
              PIVOT (MAX(A.[VALUE]) FOR A.RN IN ([1],[2],[3],[4],[5])) P
              

              【讨论】:

                【解决方案8】:

                这些都帮助我做到了这一点。我还在 2012 年,但现在有一些快速的东西可以让我拆分一个字符串,即使字符串有不同数量的分隔符,并从该字符串中获取第 n 个子字符串。它也很快。我知道这篇文章很旧,但是我花了很长时间才找到一些东西,所以希望这对其他人有帮助。

                CREATE FUNCTION [dbo].[SplitsByIndex]
                (@separator VARCHAR(20)  = ' ', 
                 @string    VARCHAR(MAX), 
                 @position  INT
                )
                RETURNS VARCHAR(MAX)
                AS
                     BEGIN
                     DECLARE @results TABLE
                     (id   INT IDENTITY(1, 1), 
                      chrs VARCHAR(8000)
                     );
                     DECLARE @outResult VARCHAR(8000);
                     WITH X(N)
                          AS (SELECT 'Table1'
                              FROM(VALUES(0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) T(C)),
                          Y(N)
                          AS (SELECT 'Table2'
                              FROM X A1, 
                                   X A2, 
                                   X A3, 
                                   X A4, 
                                   X A5, 
                                   X A6, 
                                   X A7, 
                                   X A8), -- Up to 16^8 = 4 billion
                          T(N)
                          AS (SELECT TOP (ISNULL(LEN(@string), 0)) ROW_NUMBER() OVER(
                                                                   ORDER BY
                              (
                                  SELECT NULL
                              )) - 1 N
                              FROM Y),
                          Delim(Pos)
                          AS (SELECT t.N
                              FROM T
                              WHERE(SUBSTRING(@string, t.N, LEN(@separator + 'x') - 1) LIKE @separator
                                    OR t.N = 0)),
                          Separated(value)
                          AS (SELECT SUBSTRING(@string, d.Pos + LEN(@separator + 'x') - 1, LEAD(d.Pos, 1, 2147483647) OVER(
                                     ORDER BY
                              (
                                  SELECT NULL
                              ))-d.Pos - LEN(@separator))
                              FROM Delim d
                              WHERE @string IS NOT NULL)
                          INSERT INTO @results(chrs)
                                 SELECT s.value
                                 FROM Separated s
                                 WHERE s.value <> @separator;
                     SELECT @outResult =
                     (
                         SELECT chrs
                         FROM @results
                         WHERE id = @position
                     );
                     RETURN @outResult;
                 END;
                

                可以这样使用:

                SELECT [dbo].[SplitsByIndex](' ',fieldname,2) 
                from tablename
                

                【讨论】:

                  最近更新 更多