查找子字符串/字符索引 T-sql [重复]
Posted
技术标签:
【中文标题】查找子字符串/字符索引 T-sql [重复]【英文标题】:Find substring/charindex T-sql [duplicate] 【发布时间】:2013-07-15 05:28:44 【问题描述】:我有以下变量。
DECLARE @TestConnectionString varchar(255) = 'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
我想从这个连接字符串中分离出每个属性的值。
我确定我必须使用SUBSTRING
和CHARINDEX
,但不确定如何使用。我不想硬编码每个属性的长度,因为user_id
可能是"Comeonedude"
有人可以告诉我如何提取其中一些属性作为示例吗?
同时,我会尝试看看我是否能弄清楚。
谢谢
【问题讨论】:
***.com/questions/2647/split-string-in-sql 【参考方案1】:我喜欢使用 XML 转换来拆分 TSQL 中的字符串。首选此方法,因为它不需要您在各处创建字符串拆分函数,并且根据我的经验,它的性能和扩展性都很好。这是一个SQLFiddle 示例。
DECLARE @TestConnectionString varchar(255) = 'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
SELECT
t.c.value('(property)[1]','VARCHAR(200)') AS [property]
,t.c.value('(value)[1]','VARCHAR(200)') AS [value]
FROM (
SELECT CAST('<root><pair><property>' + REPLACE(REPLACE(LEFT(@TestConnectionString,LEN(@TestConnectionString)-1),';','</value></pair><pair><property>'),'=','</property><value>') + '</value></pair></root>' AS XML) AS properties_xml
) AS i
CROSS APPLY i.properties_xml.nodes('/root/pair') AS t(c)
解释:
@TestConnectionString 被此 select 语句格式化为 XML 文档:
SELECT CAST('<root><pair><property>' + REPLACE(REPLACE(LEFT(@TestConnectionString,LEN(@TestConnectionString)-1),';','</value></pair><pair><property>'),'=','</property><value>') + '</value></pair></root>' AS XML) AS properties_xml
XML 字符串以<root><pair><property>
开头,然后REPLACE
函数将每个分隔分号替换为</value></pair><pair><property>
,并将每个分隔等号替换为</property><value>
。 @TestConnectionString 以分号结尾,因此必须首先通过 LEFT
函数删除分号,否则我们将在 XML 字符串的末尾加上一个额外的 </value></pair><pair><property>
。 XML 字符串通过附加</value></pair></root>
完成,我们最终得到:
<root>
<pair>
<property>Data Source</property>
<value>123.45.67.890</value>
</pair>
<pair>
<property>User ID</property>
<value>TestUser</value>
</pair>
<pair>
<property>Password</property>
<value>TestPassword</value>
</pair>
<pair>
<property>Initial Catalog</property>
<value>TestCatalogName</value>
</pair>
<pair>
<property>Provider</property>
<value>SQLNCLI11.1</value>
</pair>
<pair>
<property>Persist Security Info</property>
<value>True</value>
</pair>
<pair>
<property>Auto Translate</property>
<value>False</value>
</pair>
</root>
使用CAST
函数将XML 字符串转换为XML
数据类型。
CROSS APPLY
运算符可用于将 XML 文档的节点转换为具有行和列(别名为 c
)的类似表格的对象(别名为 t
)。
CROSS APPLY i.properties_xml.nodes('/root/pair') AS t(c)
现在我们有一个表格,其中的行代表 XML 文档中的每个对节点。可以从中选择该表,使用value
函数为我们要选择的每一列分配数据类型。
SELECT
t.c.value('(property)[1]','VARCHAR(200)') AS [property]
,t.c.value('(value)[1]','VARCHAR(200)') AS [value]
【讨论】:
这是一个非常好的方法。 感谢这个...小代码但非常独特的方法。 :) 试图弄清楚发生了什么......非常困惑但非常好奇......介意解释一下你的方法吗?这太棒了...不需要其他对象(即 udf),这在处理多个 sql 环境时会非常方便...希望我可以将多个 cmets 标记为答案。 我添加了一个 SQLFiddle 并更好地解释了 XMLCAST
、SELECT
和 CROSS APPLY
中发生的事情。希望对你有帮助【参考方案2】:
首先在';'处分割字符串..您可以在线找到许多拆分功能。使用将其拆分为表格的表格。
以下代码来自:How to split string using delimiter char using T-SQL?
CREATE FUNCTION [dbo].[Split]
(
@String varchar(max)
,@Delimiter char =';' -- default value
)
RETURNS @Results table
(
Ordinal int
,StringValue varchar(max)
)
as
begin
set @String = isnull(@String,'')
set @Delimiter = isnull(@Delimiter,'')
declare
@TempString varchar(max) = @String
,@Ordinal int = 0
,@CharIndex int = 0
set @CharIndex = charindex(@Delimiter, @TempString)
while @CharIndex != 0 begin
set @Ordinal += 1
insert @Results values
(
@Ordinal
,substring(@TempString, 0, @CharIndex)
)
set @TempString = substring(@TempString, @CharIndex + 1, len(@TempString) - @CharIndex)
set @CharIndex = charindex(@Delimiter, @TempString)
end
if @TempString != '' begin
set @Ordinal += 1
insert @Results values
(
@Ordinal
,@TempString
)
end
return
end
假设顺序始终相同,请在“=”处拆分每个结果。 取每个字符串的右边部分('='之后剩余字符串的长度)..
等等,你拥有每一个属性都有它的价值。
-- 编辑:使用上面的拆分函数:
DECLARE @TestConnectionString varchar(255) = 'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
create table #result
(
property varchar(255),
Value varchar(255)
)
create table #tmp
(
Property varchar(255)
)
create table #tmp2
(
Value varchar(255)
)
insert into #tmp
select * from split(@TestConnectionString, ';')
--select * from #tmp
/* Sclaufe */
declare @id varchar(255)
DECLARE a_coursor CURSOR FOR
select property from #tmp
OPEN a_coursor;
FETCH NEXT FROM a_coursor into @id;
WHILE @@FETCH_STATUS = 0
BEGIN
-- select @id
insert into #tmp2
select * from Split(@id, '=')
FETCH NEXT FROM a_coursor
INTO @id
END;
CLOSE a_coursor;
DEALLOCATE a_coursor;
select * from #tmp2
/* Sclaufe */
declare @id2 varchar(255)
declare @oldid varchar(255)
declare @count int
set @count = 1
DECLARE a_coursor CURSOR FOR
select value from #tmp2
OPEN a_coursor;
FETCH NEXT FROM a_coursor into @id2;
WHILE @@FETCH_STATUS = 0
BEGIN
print @id2
if @count % 2 <> 0
begin
insert into #result
select @id2, ''
set @oldid = @id2
end
else
begin
update #result
set Value = @id2
where property = @oldid
end
set @count = @count + 1
FETCH NEXT FROM a_coursor
INTO @id2
END;
CLOSE a_coursor;
DEALLOCATE a_coursor;
select * from #result
drop table #tmp
drop table #tmp2
drop table #result
结果将在#result 表中:
╔═══════════════════════╦═════════════════╗
║ property ║ Value ║
╠═══════════════════════╬═════════════════╣
║ Data Source ║ 123.45.67.890 ║
║ User ID ║ TestUser ║
║ Password ║ TestPassword ║
║ Initial Catalog ║ TestCatalogName ║
║ Provider ║ SQLNCLI11.1 ║
║ Persist Security Info ║ True ║
║ Auto Translate ║ False ║
╚═══════════════════════╩═════════════════╝
编辑:或者你可以创建一个存储过程:
if exists (select 1 from sysobjects where name = 'getvalue2' and type = 'P')
begin
drop procedure getvalue2
print 'Procedure: getvalue2 deleted ...'
end
go
/*
exec getvalue2 'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
*/
create procedure [dbo].[getvalue2]
( @TestConnectionString varchar(255))
as
begin
--= 'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
create table #result
(
property varchar(255),
Value varchar(255)
)
create table #tmp
(
firstrun varchar(255)
)
create table #tmp2
(
secondrun varchar(255)
)
insert into #tmp
select * from split(@TestConnectionString, ';')
--select * from #tmp
declare @id varchar(255)
DECLARE a_coursor CURSOR FOR
select firstrun from #tmp
OPEN a_coursor;
FETCH NEXT FROM a_coursor into @id;
WHILE @@FETCH_STATUS = 0
BEGIN
insert into #tmp2
select * from Split(@id, '=')
FETCH NEXT FROM a_coursor
INTO @id
END;
CLOSE a_coursor;
DEALLOCATE a_coursor;
declare @id2 varchar(255)
declare @oldid varchar(255)
declare @count int
set @count = 1
DECLARE a_coursor CURSOR FOR
select secondrun from #tmp2
OPEN a_coursor;
FETCH NEXT FROM a_coursor into @id2;
WHILE @@FETCH_STATUS = 0
BEGIN
print @id2
if @count % 2 <> 0
begin
insert into #result
select @id2, ''
set @oldid = @id2
end
else
begin
update #result
set Value = @id2
where property = @oldid
end
set @count = @count + 1
FETCH NEXT FROM a_coursor
INTO @id2
END;
CLOSE a_coursor;
DEALLOCATE a_coursor;
select * from #result
end
玩得开心, 你很高兴=)
【讨论】:
很遗憾,您不能假设顺序相同。 仍然会有一张表,其属性为“property=value, property=value ...”,因此您可以轻松地将 '"' 之前的部分作为变量的名称下一次拆分。 现在你所要做的就是稍微调整一下,这样空值和其他特殊的东西就会被接受。 添加了一个存储过程.. 您正在使用一个游标和三个(!)临时表。这有点过分了。我建议阅读有关基于集合的解决方案和RBAR【参考方案3】:使用一个通用的字符串分割函数两次(见下文)。调用一次以拆分名称-值对,再次调用以将名称与值分开。
查看实际操作:http://sqlfiddle.com/#!3/3cce5/1/0
SELECT
t3.[1] AS name,
t3.[2] AS value
FROM dbo.strsplit(@TestConnectionString,';') t1
CROSS APPLY dbo.strsplit(t1.col,'=') t2
PIVOT(MAX(t2.col) FOR t2.n IN ([1],[2])) t3
我的字符串分割函数。
CREATE FUNCTION [dbo].[strsplit](
@str varchar(max), --String to be split
@dlm char(1) --Delimiting character
)
RETURNS TABLE
RETURN (
WITH [cols] AS (
SELECT
1 AS [n],
CAST(1 AS bigint) AS [idx],
CHARINDEX(@dlm,@str,1) AS [ndx]
UNION ALL
SELECT
[n] + 1,
CHARINDEX(@dlm,@str,[idx]) + 1,
CHARINDEX(@dlm,@str,[ndx] + 1)
FROM [cols]
WHERE CHARINDEX(@dlm,@str,[idx]) > 0
)
SELECT
[n],
CASE [ndx]
WHEN 0 THEN SUBSTRING(@str,[idx],LEN(@str)-[idx]+1)
ELSE SUBSTRING(@str,[idx],[ndx]-[idx])
END AS [col]
FROM [cols])
【讨论】:
感谢分享这个方法,我试试。 :)【参考方案4】:如果您关心递归,SQL Server 可以处理。我重写了 rCTE 查询(再次)我在另一个项目中使用来提取值:
DECLARE @Test varchar(255) =
'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
;WITH T AS (
SELECT
StartIdx = CAST(0 as int),
EndIdx = CAST(0 as int),
Result = CAST('' as nvarchar(max))
UNION ALL
SELECT
StartIdx = CAST(newstartidx AS int),
EndIdx = CAST(EndIdx + newendidx as int),
Result = CAST(newtoken as nvarchar(max))
FROM
T
CROSS APPLY(
SELECT newstartidx = EndIdx + 1
) calc1
CROSS APPLY(
SELECT newtxt = substring(@Test, newstartidx, len(@Test))
) calc2
CROSS APPLY(
SELECT patidx = charindex(';', newtxt)
) calc3
CROSS APPLY(
SELECT newendidx = CASE
WHEN patidx = 0 THEN len(newtxt)
ELSE patidx END
) calc4
CROSS APPLY(
SELECT newtoken = substring(@Test, newstartidx, newendidx)
) calc5
WHERE newendidx > 0
)
SELECT
--Result,
Name = left(Result, idx - 1),
Value = substring(Result, idx + 1, len(Result) - idx - 1)
FROM
T
CROSS APPLY (
SELECT idx = charindex('=', Result)
) calc6
WHERE StartIdx != 0
【讨论】:
这也很好用!!!谢谢【参考方案5】:如果您真的想在没有硬编码数字的情况下使用SUBSTRING
,可以使用以下一般方法:
DECLARE @TestConnectionString varchar(255) = 'Data Source=123.45.67.890;User ID=TestUser;Password=TestPassword;Initial Catalog=TestCatalogName;Provider=SQLNCLI11.1;Persist Security Info=True;Auto Translate=False;'
SELECT SUBSTRING(@TestConnectionString,CHARINDEX('ID=',@TestConnectionString)+3,CHARINDEX(';Password',@TestConnectionString)-CHARINDEX('ID=',@TestConnectionString)-3) 'User ID'
,SUBSTRING(@TestConnectionString,CHARINDEX(';Password=',@TestConnectionString)+10,CHARINDEX(';Initial',@TestConnectionString)-CHARINDEX(';Password=',@TestConnectionString)-10) 'Password'
如果字符串中存在不一致,这样的方法可能会失败,可能值得根据 ;
分隔符将字符串拆分为字段。
【讨论】:
是的,感谢您在这方面的帮助并指出了一些改进。以上是关于查找子字符串/字符索引 T-sql [重复]的主要内容,如果未能解决你的问题,请参考以下文章