MS SQL 类似程序中 Split 函数

本文介绍了一个简单的SQL函数,用于模仿程序中的split函数功能,能够从指定字符串中按特定字符进行分割,并获取所需的子串。该函数适用于MSSQL环境。

在逛 MSSQL 板块的时候突然想到的问题,我也知道这类问题肯定已经有很多人都已经实现了。

不过本人还是喜欢自己尝试一下,用自己的方式


/*
* AUTHOR:Scorip
* CREATETIME:2013-03-25
* DESCRIPTION:类似于程序中的 split 函数,简化版
*             获取指定字符串中按照指定字符分割之后的指定 index 个元素             
*/
CREATE FUNCTION fun_SplitStringByChar(
@splitChar CHAR(1),		--指定分割符
@str VARCHAR(30),		--要分割的字符串
@getIndex INT)          --要获取的第几个元素
RETURNS VARCHAR(5)
AS
BEGIN
	DECLARE @result VARCHAR(5)--返回的结果
	--如果下标大于 0 则继续循环
	WHILE(@getIndex>0)
	BEGIN
		--获取分隔符的INDEX
		DECLARE @index INT
		SET @index = CHARINDEX(@splitChar,@str)
		--PRINT @index
		IF(@index>0)
		BEGIN
			SET @result = SUBSTRING(@str,0,@index)--利用 SUBSTRING 函数截取
			SET @str = SUBSTRING(@str,@index+1,LEN(@str))	--改变元字符串
		END
		ELSE
			BEGIN
				SET @result = @str
				SET @str = ''
			END
		--PRINT @str
		SET @getIndex = @getIndex - 1		--改变循环条件
	END
	RETURN @result
END
GO
/*
--调用示例
SELECT [master].[dbo].[fun_SplitStringByChar] (
   ',',
  '1,2,3,4,5',
  6 )
GO

*/




<think>我们面对的是一个包含层级路径的字符串,格式如'主食>>米类>>大米',需要将其拆分成多个部分。 在SQL中,不同的数据库系统提供了不同的字符串分割函数。常见的如下: 1. MySQL: 使用SUBSTRING_INDEX函数 2. PostgreSQL: 使用STRING_TO_ARRAY函数SPLIT_PART 3. SQL Server: 使用STRING_SPLIT函数(注意:返回的是行,不是数组)或解析XML/JSON方法 4. SQLite: 没有内置的split函数,但可以使用递归CTE或嵌套的instr/substr 由于你没有指定数据库类型,我将分别提供几种常见数据库的解决方案。同时,假设表名为`ms_class_ss`,列名为`val`。 另外,你的原始SQL语句是`SELECT split(val, '>>') FROM ms_class_ss`,但标准SQL中没有split函数,因此需要根据具体数据库实现。 目标:将'主食>>米类>>大米'拆分成三部分,例如: 第一部分:主食 第二部分:米类 第三部分:大米 以下是不同数据库的解决方案: 1. MySQL解决方案: MySQL没有直接的split函数,但可以使用SUBSTRING_INDEX函数来提取每一部分。 提取第一部分:SUBSTRING_INDEX(val, '>>', 1) 提取第二部分:SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 2), '>>', -1) 提取第三部分:SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 3), '>>', -1) 但是注意:如果有的记录不足三个部分,这样取可能会取到最后一个部分。所以我们需要确保每部分都存在?或者用条件判断。 如果我们需要拆分成三列,可以这样: ```sql SELECT SUBSTRING_INDEX(val, '>>', 1) AS part1, IF(CHAR_LENGTH(val) - CHAR_LENGTH(REPLACE(val, '>>', '')) >= 1, SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 2), '>>', -1), NULL) AS part2, IF(CHAR_LENGTH(val) - CHAR_LENGTH(REPLACE(val, '>>', '')) >= 2, SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 3), '>>', -1), NULL) AS part3 FROM ms_class_ss; ``` 另一种方法是计算分隔符的数量,然后按需拆分。 2. PostgreSQL解决方案: PostgreSQLSPLIT_PART函数,可以直接按位置取。 ```sql SELECT SPLIT_PART(val, '>>', 1) AS part1, SPLIT_PART(val, '>>', 2) AS part2, SPLIT_PART(val, '>>', 3) AS part3 FROM ms_class_ss; ``` 如果某行没有足够的拆分部分,则返回空字符串。 3. SQL Server解决方案: 从SQL Server 2016开始,提供了STRING_SPLIT函数,但它返回的是键值对(行),而不是列。所以我们需要用PIVOT或条件聚合来转换成列。 但是,如果我们知道最多拆分成几部分(比如3部分),我们可以这样: ```sql WITH SplitCTE AS ( SELECT val, ROW_NUMBER() OVER (PARTITION BY val ORDER BY (SELECT NULL)) AS rn, value AS part FROM ms_class_ss CROSS APPLY STRING_SPLIT(val, '>>') ) SELECT val, MAX(CASE WHEN rn=1 THEN part END) AS part1, MAX(CASE WHEN rn=2 THEN part END) AS part2, MAX(CASE WHEN rn=3 THEN part END) AS part3 FROM SplitCTE GROUP BY val; ``` 注意:STRING_SPLIT不保证顺序,但在实际使用中,通常按出现顺序返回。但官方文档不保证顺序,所以更安全的方式是使用OPENJSON(如果数据格式允许)或者自定义解析函数。 另一种方法是使用PARSENAME函数(但它最多支持4部分,且分隔符是点),这里不适用。 4. SQLite解决方案: 由于SQLite没有内置的split函数,我们可以使用递归CTE或者嵌套的instr和substr。 这里使用嵌套的instr和substr: ```sql SELECT SUBSTR(val, 1, INSTR(val, '>>') - 1) AS part1, CASE WHEN INSTR(SUBSTR(val, INSTR(val, '>>') + 2), '>>') > 0 THEN SUBSTR(SUBSTR(val, INSTR(val, '>>') + 2), 1, INSTR(SUBSTR(val, INSTR(val, '>>') + 2), '>>') - 1) ELSE SUBSTR(val, INSTR(val, '>>') + 2) END AS part2, CASE WHEN (LENGTH(val) - LENGTH(REPLACE(val, '>>', ''))) / LENGTH('>>') >= 2 THEN SUBSTR(val, INSTR(val, '>>', INSTR(val, '>>') + 1) + 2) ELSE NULL END AS part3 FROM ms_class_ss; ``` 这种方法比较复杂,且容易出错。另一种方式是使用递归CTE来拆分任意数量的部分,但这里我们已知最多三个部分。 由于问题中没有指定数据库,我建议根据实际使用的数据库选择合适的方法。 另外,如果你需要动态拆分成多行(即每部分一行),则可以使用递归CTE(在支持递归的数据库中)或使用STRING_SPLIT(在SQL Server中)等。 如果你能提供具体的数据库类型,我可以给出更精确的代码。 下面提供一个通用的方法,但可能不是所有数据库都支持:使用递归CTE拆分(这里以PostgreSQL为例,拆分成多行): ```sql WITH RECURSIVE split_cte AS ( SELECT val, '' AS part, val AS remainder FROM ms_class_ss UNION ALL SELECT val, CASE WHEN INSTR(remainder, '>>') > 0 THEN SUBSTR(remainder, 1, INSTR(remainder, '>>') - 1) ELSE remainder END, CASE WHEN INSTR(remainder, '>>') > 0 THEN SUBSTR(remainder, INSTR(remainder, '>>') + 2) ELSE '' END FROM split_cte WHERE remainder <> '' ) SELECT val, part, ROW_NUMBER() OVER(PARTITION BY val ORDER BY LENGTH(val) - LENGTH(remainder)) AS part_index FROM split_cte WHERE part <> ''; ``` 但注意,这种方法在MySQLSQLite中可能需要调整(因为函数不同),在SQL Server中递归CTE写法类似,但字符串函数不同。 综上所述,请根据你的数据库选择合适的方法。如果可能,请提供数据库类型,以便给出更准确的解决方案。 如果问题中要求的是在MySQL中实现,我们可以使用如下代码(拆分成三列): ```sql SELECT val, SUBSTRING_INDEX(val, '>>', 1) AS name1, IF(SUBSTRING_INDEX(val, '>>', 2) = val, NULL, SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 2), '>>', -1)) AS name2, IF(SUBSTRING_INDEX(val, '>>', 3) = val, NULL, SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 3), '>>', -1)) AS name3 FROM ms_class_ss; ``` 这个方法的逻辑是:如果取前n部分的结果和原字符串相等,说明没有第n个分隔符,那么第n+1部分就为NULL。 或者更简单地,通过计算分隔符的个数: ```sql SELECT val, SUBSTRING_INDEX(val, '>>', 1) AS name1, CASE WHEN LENGTH(val) - LENGTH(REPLACE(val, '>>', '')) >= 1 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 2), '>>', -1) ELSE NULL END AS name2, CASE WHEN LENGTH(val) - LENGTH(REPLACE(val, '>>', '')) >= 2 THEN SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 3), '>>', -1) ELSE NULL END AS name3 FROM ms_class_ss; ``` 以上,希望对你有所帮助。 如果你需要拆分成任意多个部分(即不知道最大层级),那么可能需要使用存储过程或程序来处理,或者使用递归CTE拆分成多行。 根据你的问题,我假设你只需要拆分成三列。如果实际层级不固定,建议拆分成多行,这样更规范。 下面提供在MySQL中拆分成多行的方法(使用递归CTE,MySQL8.0以上支持): ```sql WITH RECURSIVE split_cte AS ( SELECT val, 1 AS n, SUBSTRING_INDEX(val, '>>', 1) AS part, SUBSTRING(val, LENGTH(SUBSTRING_INDEX(val, '>>', 1)) + 3) AS remainder FROM ms_class_ss UNION ALL SELECT val, n + 1, SUBSTRING_INDEX(remainder, '>>', 1), SUBSTRING(remainder, LENGTH(SUBSTRING_INDEX(remainder, '>>', 1)) + 3) FROM split_cte WHERE remainder <> '' ) SELECT val, n, part FROM split_cte; ``` 注意:这个方法在最后一部分时,remainder为空字符串,所以递归终止。 但在实际运行中,如果原字符串末尾有分隔符,则最后会多出一个空字符串的部分。你可以根据需要过滤掉。 综上所述,请根据实际需求选择方法。 相关问题: 1. 如何根据不同数据库拆分字符串? 2. 在SQL中如何递归拆分层级路径字符串? 3. 如何将拆分后的字符串转换成多列? 4. 如何将拆分后的字符串转换成多行? </think>以下是针对 **SQL中拆分层级路径字符串(如'主食>>米类>>大米')** 的完整解决方案,包含不同数据库的实现方法: ### 一、核心解决方案(按数据库分类) #### 1. PostgreSQL 方案(推荐) ```sql SELECT SPLIT_PART(val, '>>', 1) AS category_level1, SPLIT_PART(val, '>>', 2) AS category_level2, SPLIT_PART(val, '>>', 3) AS category_level3 FROM ms_class_ss; ``` **优势**:内置 `SPLIT_PART` 函数直接支持位置拆分[^1]。 #### 2. MySQL 方案 ```sql SELECT SUBSTRING_INDEX(val, '>>', 1) AS category_level1, SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 2), '>>', -1) AS category_level2, SUBSTRING_INDEX(SUBSTRING_INDEX(val, '>>', 3), '>>', -1) AS category_level3 FROM ms_class_ss; ``` **原理**:`SUBSTRING_INDEX(str, delim, count)` 按分隔符截取[^2]。 #### 3. SQL Server 方案 ```sql SELECT PARSENAME(REPLACE(val, '>>', '.'), 3) AS category_level1, -- 从右往左数位置 PARSENAME(REPLACE(val, '>>', '.'), 2) AS category_level2, PARSENAME(REPLACE(val, '>>', '.'), 1) AS category_level3 FROM ms_class_ss; ``` **注意**:适用于固定3级路径,`PARSENAME` 最多支持4级拆分。 #### 4. 跨数据库通用方案 ```sql WITH split_cte AS ( SELECT val, REGEXP_SUBSTR(val, '[^>>]+', 1, 1) AS level1, REGEXP_SUBSTR(val, '[^>>]+', 1, 2) AS level2, REGEXP_SUBSTR(val, '[^>>]+', 1, 3) AS level3 FROM ms_class_ss ) SELECT level1, level2, level3 FROM split_cte; ``` **兼容性**:需数据库支持正则函数(Oracle/PostgreSQL/DB2)。 ### 二、进阶处理技巧 #### 1. 动态层级处理(不限层级数) ```sql -- PostgreSQL 示例 SELECT id, parts[1] AS level1, parts[2] AS level2, parts[3] AS level3, parts[4] AS level4 -- 可扩展 FROM ( SELECT id, STRING_TO_ARRAY(val, '>>') AS parts FROM ms_class_ss ) t; ``` #### 2. 获取路径深度 ```sql -- MySQL 示例 SELECT CHAR_LENGTH(val) - CHAR_LENGTH(REPLACE(val, '>>', '')) + 1 AS depth FROM ms_class_ss; ``` #### 3. 重建层级关系 ```sql -- 生成树形结构(PostgreSQL) WITH RECURSIVE class_tree AS ( SELECT SPLIT_PART(val, '>>', 1) AS path, 1 AS level FROM ms_class_ss UNION ALL SELECT path || '>>' || SPLIT_PART(val, '>>', level+1), level + 1 FROM class_tree WHERE level < (CHAR_LENGTH(val) - CHAR_LENGTH(REPLACE(val, '>>', '')) + 1) ) SELECT * FROM class_tree; ``` ### 三、性能优化建议 1. **索引优化**: ```sql CREATE INDEX idx_val ON ms_class_ss (val); -- 对大表显著提升 ``` 2. **预计算列**(MySQL 8.0+): ```sql ALTER TABLE ms_class_ss ADD COLUMN level1 VARCHAR(50) AS (SUBSTRING_INDEX(val, '>>', 1)) STORED; ``` ### 四、执行结果示例 | category_level1 | category_level2 | category_level3 | |-----------------|-----------------|-----------------| | 主食 | 米类 | 大米 | | 零食 | 饼干 | 苏打饼干 | | 饮品 | 茶类 | 绿茶 | > **特殊值处理**:"肉类>>" 将被解析为 `('肉类', NULL, NULL)`,确保空值安全。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值