[英]Split function in SQL Server 2008
I have Table1
with columns like this: 我有Table1
的列:
+--+------+
|ID|Name |
+--+------+
|1 |MSSQL |
+--+------+
|2 |MySQl |
+--+------+
|3 |Oracle|
+--+------+
In Table2
, I have a column like 在表Table2
,我有一个列
+------------+
|Databasename|
+------------+
|1,3 |
+------------+
|2 |
+------------+
|1,2 |
+------------+
My output should be: 我的输出应该是:
+------------+
|Databasename|
+------------+
|MSSQL,Oracle|
+------------+
|MySQL |
+------------+
|MSSQL,MYSQL |
+------------+
How do I get this, I need query for this.. 我怎么得到这个,我需要查询这个..
You are asking for a split function but you do not have to split your values to get the result you want. 您要求分割功能,但您不必分割您的值以获得所需的结果。
This query builds the comma separated names list in a correlated subquery using the for xml
trick to concatenate values. 此查询使用for xml
技巧在相关子查询中构建逗号分隔名称列表以连接值。 It uses like
to figure out what values to use from Table1
for each row in Table2
. 它使用like
弄清楚什么值从使用Table1
中的每一行Table2
。
select (
select ', '+T1.Name
from Table1 as T1
where ','+T2.Databasename+',' like '%,'+cast(T1.ID as varchar(10))+',%'
for xml path(''), type
).value('substring(text()[1], 3)', 'varchar(max)') as Databasenames
from Table2 as T2
First, your best solution is to not store data in a comma-separated list in your database. 首先,您最好的解决方案是不将数据存储在数据库中以逗号分隔的列表中。 You should consider fixing the table structure. 您应该考虑修复表结构。
If you cannot alter the table structure, then you will need to split the data in the list to rows to assign the correct name. 如果无法更改表结构,则需要将列表中的数据拆分为行以指定正确的名称。 Once the data is split then you can concatenate the data back into the list. 分割数据后,您可以将数据连接回列表。
There are many different split
function that you can find online but here is a version that I typically use: 您可以在线找到许多不同的split
功能,但这是我通常使用的版本:
CREATE FUNCTION [dbo].[Split](@String varchar(MAX), @Delimiter char(1))
returns @temptable TABLE (items varchar(MAX))
as
begin
declare @idx int
declare @slice varchar(8000)
select @idx = 1
if len(@String)<1 or @String is null return
while @idx!= 0
begin
set @idx = charindex(@Delimiter,@String)
if @idx!=0
set @slice = left(@String,@idx - 1)
else
set @slice = @String
if(len(@slice)>0)
insert into @temptable(Items) values(@slice)
set @String = right(@String,len(@String) - @idx)
if len(@String) = 0 break
end
return
end;
To get your result, I would start by applying the split
function and a row_number()
since I do not see a unique key associated with each row. 为了得到你的结果,我将首先应用split
函数和row_number()
因为我没有看到与每一行相关联的唯一键。 If you have a unique key on each row then you will not need the row_number()
: 如果每行都有唯一键,则不需要row_number()
:
;with cte as
(
select rn, name, id
from
(
select row_number() over(order by (select 1)) rn,
databasename
from table2
) t2
cross apply dbo.split(t2.databasename, ',') i
inner join table1 t1
on i.items = t1.id
)
select *
from cte
This query breaks your comma-separated list into the following: 此查询将以逗号分隔的列表分为以下内容:
| RN | NAME | ID |
--------------------
| 1 | MSSQL | 1 |
| 1 | Oracle | 3 |
| 2 | MySQl | 2 |
| 3 | MSSQL | 1 |
| 3 | MySQl | 2 |
Once you have the data in multiple rows with the correct name
, then you can use STUFF()
and FOR XML PATH
to concatenate it into the list. 一旦使用正确的name
将数据存储在多个行中,就可以使用STUFF()
和FOR XML PATH
将其连接到列表中。 You full query would be similar to this: 您完全查询将类似于:
;with cte as
(
select rn, name, id
from
(
select row_number() over(order by (select 1)) rn,
databasename
from table2
) t2
cross apply dbo.split(t2.databasename, ',') i
inner join table1 t1
on i.items = t1.id
)
select
STUFF(
(SELECT ', ' + c2.name
FROM cte c2
where c1.rn = c2.rn
order by c2.id
FOR XML PATH (''))
, 1, 1, '') Databasename
from cte c1
group by c1.rn
order by c1.rn;
See SQL Fiddle with Demo . 请参阅SQL Fiddle with Demo 。
The result of the full query is: 完整查询的结果是:
| DATABASENAME |
------------------
| MSSQL, Oracle |
| MySQl |
| MSSQL, MySQl |
No splitting, also no XML Path, but achieves the right result. 没有拆分,也没有XML路径,但实现了正确的结果。
;with cte as (
select *, cast(null as varchar(1024)) as str, cast(0 as int) as ID
from Table2
union all
select DatabaseName, (case when DatabaseName like cast(t.ID as varchar(32)) + ',%'
or DatabaseName like '%,' + cast(t.ID as varchar(32)) + ',%'
or DatabaseName like '%,' + cast(t.ID as varchar(32))
or DatabaseName = cast(t.ID as varchar(32)) then cast(isnull(str, '') + ',' + t.Name as varchar(1024)) else str end), cte.ID + 1 as ID
from cte
inner join Table1 t on cte.ID + 1 = t.ID
)
select DatabaseName, (case when str like ',%' then substring(str, 2, len(str)) else null end) as str
from cte c
where ID = (select max(ID) from cte where DatabaseName = c.DatabaseName)
--Here it goes:
----------------
-- FieldCount --
----------------
CREATE FUNCTION [dbo].[FieldCount](@S VARCHAR(8000), @Separator VARCHAR(10))
RETURNS INT
AS
BEGIN
/*
@Author: Leonardo Augusto Rezende Santos
@Contact: http://www.linkedin.com/pub/leonardo-santos/0/2b1/890
*/
DECLARE @Ptr INT, @p INT, @LenS INT, @LenSep INT, @Result INT
IF @Separator = ' '
BEGIN
SET @S = REPLACE(@S, ' ', '|-|')
SET @Separator = '|-|'
END
WHILE CHARINDEX(@Separator + @Separator, @S) > 0
SET @S = Replace(@S, @Separator + @Separator, @Separator + '_-_' + @Separator)
IF @S <> ''
SET @Result = 1
ELSE
BEGIN
SET @Result = 0
RETURN(@Result)
END
SET @Ptr = 0
SET @LenS = LEN(@S)
SET @LenSep = LEN(@Separator)
SET @p = CHARINDEX(@Separator, @S)
WHILE @p > 0
BEGIN
SET @Result = @Result + 1
SET @Ptr = @Ptr + @p + @LenSep
SET @p = CHARINDEX(@Separator, SUBSTRING(@S, @Ptr, @LenS - @Ptr + 1))
END
RETURN(@Result)
END
--------------
-- GetField --
--------------
CREATE FUNCTION [dbo].[GetField](@S VARCHAR(8000), @Separator VARCHAR(10), @Field INT)
RETURNS VARCHAR(8000)
AS
BEGIN
/*
@Author: Leonardo Augusto Rezende Santos
@Contact: http://www.linkedin.com/pub/leonardo-santos/0/2b1/890
*/
DECLARE @Ptr INT, @p INT, @LenS INT, @LenSep INT, @Fld INT, @Result VARCHAR(8000)
IF @Separator = ' '
BEGIN
SET @S = REPLACE(@S, ' ', '|-|')
SET @Separator = '|-|'
END
IF @Field > dbo.FieldCount(@S, @Separator)
BEGIN
SET @Result = ''
RETURN(@Result)
END
SET @Fld = 1
SET @Ptr = 1
SET @LenS = LEN(@S)
SET @LenSep = LEN(@Separator)
SET @p = CHARINDEX(@Separator, @S)
WHILE (@p > 0) and (@Fld < @Field)
BEGIN
SET @Fld = @Fld + 1
SET @Ptr = @Ptr + @p + @LenSep - 1
SET @p = CHARINDEX(@Separator, SUBSTRING(@S, @Ptr, @LenS - @Ptr + 1))
END
IF (@p = 0) and (@Fld = @Field)
SET @p = @LenS - @Ptr + 2
SET @Result = SUBSTRING(@S, @Ptr, @p - 1)
RETURN(@Result)
END
/* USAGE*/
select dbo.FieldCount('A1 A2 A3 A4 A5', ' ')
--It will return 5
select dbo.GetField('A1 A2 A3 A4 A5', ' ', 3)
--It will return 'A3'
select dbo.GetField('A1/A2/A3/A4/A5', '/', 3)
--It will return 'A3'
--Hope it works for you.
--Leonardo Augusto
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.