字符串拆分为列表(函数)

今天试著自己实现了将字符串拆分为列表的功能,中间也有找一些资料,但我知道光看是没有用的,必须要自己试著实现印象才会深刻,必须要在实现过程中遇到问题然后解决问题,反复折腾过才算真正懂了!

拆分字符串这种数据处理功能性的代码,写成函数比较合适。

我写的第一个版本(有局限):

/*
思路:遍历字符串,保存被分隔字符,遇到分隔符后播入结果表,再重新开始保存新的被分隔字符
*/
if object_id('dbo.f_MySplit_1','TF') is not null
    drop function dbo.f_MySplit_1
go
create function dbo.f_MySplit_1
(
    @str varchar(1000),   -- 待拆分字符串
    @delimiter varchar(5) -- 分隔符(仅限一个字符)
)
returns @split table(item varchar(100))
as
begin
    set @str = @str + @delimiter   -- 便于统一处理

    declare @len int, @fo int, @tmp varchar(100)
    select @tmp = '',@len = len(@str + 'x') - 1, @fo = 1

    while @fo  @delimiter)
                begin
                    set @tmp = @tmp + substring(@str,@fo,1)
                end
            else
                begin
                    insert into @split values(@tmp)
                    set @tmp = ''
                end
            set @fo = @fo + 1
        end
    return
end
-- 测试
select * from testdb.dbo.MySplit_1('1,t5655565,9tt,10,a,bgfr,c',',')
item
----------
1
t5655565
9tt
10
a
bgfr
c
(7 row(s) affected)

期本功能是实现了,可以进行常规的拆分,不过在多试验了几个用例之后发现,它有相当的局限性,甚至可以说是bug

1、仅支持单个分隔符

2、没考虑 分隔符 为 null 或 空字符 的情况( 这种情况下,每一个字符被分隔为一列

于是又做了更新,下面是第二个版本,基本上算是通用了:

/*
思路:定位前后分隔符位置,提取前后分隔之间的字符串
*/
if object_id('dbo.f_MySplit_2','TF') is not null
	drop function dbo.f_MySplit_2
go
 
create function dbo.f_MySplit_2
(
    @str varchar(1000),   -- 待拆分字符串
    @split varchar(100) = ''   -- 分隔符
)
returns @result table(item varchar(100))
as
begin    
    set @str = isnull(@split,'') + @str + isnull(@split,'')
    
    declare 
        @start_index int,   -- 子串搜索开始点
        @split_len int,     -- 分隔符长度
        @fore_location int, -- 前一个分隔符的位置
        @back_location int  -- 后一个分隔符的位置
    
    select
        @start_index = 1,
        @split_len = len(@split + 'x') - 1,  -- len()不包含尾随空格
        @fore_location = 1,
        @back_location = 1
    
    if len(isnull(@split,'') + 'x') - 1 = 0  -- 若 @split 为 null 或 ''
		begin
			declare @fo int
			set @fo = 1
			while @fo <= len(@str)
				begin
					insert into @result
					select substring(@str,@fo,1)
					set @fo = @fo + 1
				end
		end
	else
        while (@start_index + @split_len  - 1) < len(@str)
            begin
                set @fore_location = charindex(@split,@str,@start_index)
                set @back_location = charindex(@split,@str,@start_index + @split_len)
                
                insert into @result
                select substring(@str, 
                                @fore_location + @split_len,                  -- 取子串开始点
                                @back_location - @fore_location - @split_len) -- 子串长度
                                
                set @start_index = @back_location
            end
    return
end
 
-- 测试
select * from f_MySplit_2('I,@,like,@,coding',',@,')
select * from f_MySplit_2('1,2,3,4','')
select * from f_MySplit_2('1,2,3,4',null)
select * from f_MySplit_2('1,2,3,4',default)
均能得到预期结果

上面代码中两个细节说明一下:

1、set @str = isnull(@split,’’) + @str + isnull(@split,’’) ,防止 @split 为空的情况,null 与任何字符串相加的结果都为 null

2、@split_len = len(@split + ‘x’) - 1, 为什么不直接len((@split)呢,因为len(@string)默认不计算尾随的空格,这样处理一下后就变向支持了。

另一版本,相对好理解一点:

CREATE FUNCTION f_Split_V2
(
	@str VARCHAR(500),
	@delimiter VARCHAR(10)
)
RETURNS @result TABLE(items VARCHAR(100))
AS
	BEGIN
		DECLARE @index INT;

		IF ISNULL(@delimiter+'x','x')='x' --当分隔符号为''或NULL(这是一个技巧)
			BEGIN
				WHILE LEN(@str+'x') > 1
					BEGIN
						INSERT INTO @result VALUES(SUBSTRING(@str,1,1));
						SET @str = RIGHT(@str,LEN(@str+'x')-2);
					END
				RETURN;
			END

		SET @index = CHARINDEX(@delimiter,@str);
		WHILE @index <> 0
			BEGIN
				INSERT INTO @result VALUES(LEFT(@str,@index-1));
				--SET @str = RIGHT(@str,LEN(@str+'x')-LEN(@delimiter+'x')-@index+1);
				SET @str = STUFF(@str,1,@index+LEN(@delimiter)-1,'')
				SET @index = CHARINDEX(@delimiter,@str);
			END
		INSERT INTO @result VALUES(@str);

		RETURN;
	END

后来在网上看到了另一种奇特的方法,除了不支持分隔为空或null外,其余都支持,只是不好写为函数,可以考虑用存储过程,真的很简洁很强大。
与大家共享一下:

-- 巧用 union 替换分隔符,拆分字符串为列表
declare @str varchar(500), @split varchar(100), @sql varchar(800)
select @str = '小明呵呵又变帅呵呵了',@split = '呵呵'

set @sql = 'select ''' + replace(@str,@split,''' union all select ''') + ''''
--print @sql
exec(@sql)

------
小明
又变帅

(3 row(s) affected)

感谢大神们的奇思妙想,感谢互联网!!!