【问题标题】:Split string by character per character occurrence每个字符出现按字符拆分字符串
【发布时间】:2017-09-13 09:06:10
【问题描述】:

SSMS 2016 企业版

如果我有一列包含以下字符串:

$/Harmony/Maintenance/6.0/Dev/

如何创建以下结果

Column1 | Column 2 | Column 3 | Column 4 | Column 5 

   $      Harmony   Maintenance   6.0         Dev

字符串中/ 的最大数量为 27。因此,在每一行中,/ 的数量以及它们之间的字符串可能会有所不同。

我需要创建 27 列并填充它们,如果字符串只有 5 列,那么下表将为空。

我尝试了使用 CHARINDEXSUBSTRING 的解决方案,但到目前为止我无法让它按我的意愿工作。

谢谢你们!

【问题讨论】:

标签: sql sql-server string tsql split


【解决方案1】:

我会先创建一个函数来分割你的字符串,然后再旋转它。

功能:

CREATE FUNCTION [dbo].[dba_parseString_udf] (
      @stringToParse VARCHAR(8000)  
    , @delimiter     CHAR(1)
)
RETURNS @parsedString TABLE (stringValue VARCHAR(128)) AS
BEGIN

/* Declare variables */
DECLARE @trimmedString  VARCHAR(8000);

/* We need to trim our string input in case the user entered extra spaces */
SET @trimmedString = LTRIM(RTRIM(@stringToParse));

/* Let's create a recursive CTE to break down our string for us */
WITH parseCTE (StartPos, EndPos)
AS
(
    SELECT 1 AS StartPos
        , CHARINDEX(@delimiter, @trimmedString + @delimiter) AS EndPos
    UNION ALL
    SELECT EndPos + 1 AS StartPos
        , CHARINDEX(@delimiter, @trimmedString + @delimiter , EndPos + 1) AS EndPos
    FROM parseCTE
    WHERE CHARINDEX(@delimiter, @trimmedString + @delimiter, EndPos + 1) <> 0
)

/* Let's take the results and stick it in a table */  
INSERT INTO @parsedString
SELECT SUBSTRING(@trimmedString, StartPos, EndPos - StartPos)
FROM parseCTE
WHERE LEN(LTRIM(RTRIM(SUBSTRING(@trimmedString, StartPos, EndPos - StartPos)))) > 0
OPTION (MaxRecursion 8000);

RETURN;   
END

在此之后,我将调整您的结果:

----For test purpose-----
DECLARE @Table TABLE (
ID int null,
string nvarchar(500) null
)

INSERT into @Table (ID,String)
VALUES(1,'$/Harmony/Maintenance/6.0/Dev/'),
(2,'$/Harmony/Maintenance/6.0/Dev/Test/')
--------------------------



 select [Column1],[Column2],[Column3],[Column4],[Column5],[Column6],
[Column7],[Column8],[Column9],[Column10],[Column11],[Column12],[Column13]
,[Column14],[Column15],[Column16],[Column17],[Column18],[Column19],
[Column20],[Column21],[Column22],[Column23],[Column24],[Column25],
[Column26],[Column27]
 FROM 
(
 SELECT 'Column' + cast(Row_number() over(Partition BY ID order by ID) as 
varchar(50)) as ColumnRow,
* FROM @Table
CROSS APPLY dbo.[dba_parseString_udf](string,'/')
) AS SOURCETABLE
PIVOT
(
min(stringValue)
FOR ColumnRow IN ([Column1],[Column2],[Column3],[Column4],[Column5],
[Column6],[Column7],[Column8],[Column9],[Column10],[Column11],[Column12],
[Column13]
,[Column14],[Column15],[Column16],[Column17],[Column18],[Column19],
[Column20],[Column21],[Column22],[Column23],[Column24],[Column25],
[Column26],[Column27])
)
AS
PivotTable

【讨论】:

  • 这看起来太棒了!但是当我在函数内部手动输入字符串时,如何使用这个函数来引用一个列并完成它的作用呢?
  • 我将对脚本进行一些更改。等一下:)
  • 再看一下我的脚本,我稍微编辑了一下。我希望您的列上有某种 ID,因为我用它来对列进行排序。
  • 抱歉,我的分区中使用 stringvalue 进行了排序,现在将其删除,否则排序错误:)
  • 嘿PlaidDK!我有三个解决方案,一个是其中之一,非常感谢!我将测试所有三个中的最佳性能,并检查哪一个为我提供了最快的解决方案。无论如何,这太棒了,非常感谢!
【解决方案2】:

另一种选择是使用一点 XML 和 CROSS APPLY。

很容易扩展到 27... 模式很简单

编辑 - 添加了 2ndToLast - 还添加了可选的 NullIf()

示例

Select B.*
      ,[2ndToLast] = reverse(Cast('<x>' + replace((Select replace(reverse(A.SomeCol),'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).value('/x[3]','varchar(max)'))
 From   @YourTable A
 Cross  Apply (
                Select Pos1 = nullif(xDim.value('/x[1]','varchar(max)'),'')
                      ,Pos2 = nullif(xDim.value('/x[2]','varchar(max)'),'')
                      ,Pos3 = nullif(xDim.value('/x[3]','varchar(max)'),'')
                      ,Pos4 = nullif(xDim.value('/x[4]','varchar(max)'),'')
                      ,Pos5 = nullif(xDim.value('/x[5]','varchar(max)'),'')
                      ,Pos6 = nullif(xDim.value('/x[6]','varchar(max)'),'')
                      ,Pos7 = nullif(xDim.value('/x[7]','varchar(max)'),'')
                      ,Pos8 = nullif(xDim.value('/x[8]','varchar(max)'),'')
                      ,Pos9 = nullif(xDim.value('/x[9]','varchar(max)'),'')
                From  (Select Cast('<x>' + replace((Select replace(A.SomeCol,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A 
             ) B

退货

【讨论】:

  • XML 路径唯一的问题是它使用你的 CPU 到最大,如果有很多行,你会遇到问题。
  • 这个很漂亮。简单,做的工作,只需要检查性能,但这太棒了
  • @LuisDoCantoBrum 很想知道它在您的真实数据上的表现
  • @JohnCappelletti 将在 5 分钟内对其进行测试,并通知您!
  • 顺便说一句,约翰,是否有可能有这样的:2ndLastNonNullPos,这将是一个列,将给出倒数第二个非空字符串(所以,不是最后一个位置,而是前一个到最后)
【解决方案3】:

它需要一些编码,但我的总体思路是:

动态创建查询,然后执行。

我提供了解决方案,您可以申请。我基于固定字符串(您提供的),将其分配给@str 变量并对其进行了修改。但是你可以把它全部放在一个循环中,在循环的每次运行中你都可以为它分配一个新的字符串,它将以给定的方式进行转换。每次循环执行后,都会运行插入查询。

create table #resultTable(
col1 varchar(50), col2 varchar(50), col3 varchar(50), col4 varchar(50), col5 varchar(50), col6 varchar(50), col7 varchar(50),
col8 varchar(50), col9 varchar(50), col10 varchar(50), col11 varchar(50), col12 varchar(50), col13 varchar(50), col14 varchar(50),
col15 varchar(50), col16 varchar(50), col17 varchar(50), col18 varchar(50), col19 varchar(50), col20 varchar(50), col21 varchar(50),
col22 varchar(50), col23 varchar(50), col24 varchar(50), col25 varchar(50), col26 varchar(50), col27 varchar(50)
)

declare @tableToSplit table(id int identity(1,1), ColumnWithStringToSplit  varchar(2000))
insert into @tableToSplit values ('$/Harmony/Maintenance/6.0/Dev/'),
('$/Harmony/Maintenance/6.0/Dev/123/'),('$/Harmony/Maintenance/6.0/Dev/123/456/')
select * from @tableToSplit

declare @howManyRows int, @id int, @iterator int = 1
select @howManyRows = count(*) from @tableToSplit

declare @i int, @counter int, @values varchar(1000), @query nvarchar(2000)

while @iterator <= @howManyRows
begin
    set @query = 'insert into #resultTable ('
    --get values from table
    select top 1 @id = id, @values = ColumnWithStringToSplit from 
    @tableToSplit
    --count how many strings we will have, which is equal to number of '/' in string
    set @i = len(@values) - len(replace(@values,'/',''))
    --get rid of last forward-slash
    set @values = left(@values, len(@values) - 1)
    --replace all slashes, so it can be used as part of insert statement
    set @values = '(''' + REPLACE(@values, '/', ''',''') + ''')'

    set @counter = 1
    while @counter <= @i
    begin
        set @query = @query + 'col' + cast(@counter as varchar(2)) + ','

        set @counter = @counter + 1
    end
    --get rid of last comma and append rest of the query
    set @query = left(@query, len(@query) - 1) + ') values ' + @values

    exec sp_executesql @query
    --we processed that row, so we need to delete it
    delete from @tableToSplit where id = @id
    set @iterator = @iterator + 1
end

select * from #resultTable

这是结果的图片(如果您想知道的话,在第 27 列):

【讨论】:

  • 我只是不知道如何使它适应我有一个带有 ID 的表的情况 | ColumnWithStringToSplit
  • 嘿,米哈尔!我有三个解决方案,一个是其中之一,非常感谢!我将测试所有三个中的最佳性能,并检查哪一个为我提供了最快的解决方案。无论如何,这太棒了,非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-20
  • 2012-03-17
  • 1970-01-01
相关资源
最近更新 更多