我有Table1
这样的列:
+--+------+
|ID|Name |
+--+------+
|1 |MSSQL |
+--+------+
|2 |MySQl |
+--+------+
|3 |Oracle|
+--+------+
在Table2
中,我有一个像
+------------+
|Databasename|
+------------+
|1,3 |
+------------+
|2 |
+------------+
|1,2 |
+------------+
我的输出应该是:
+------------+
|Databasename|
+------------+
|MSSQL,Oracle|
+------------+
|MySQL |
+------------+
|MSSQL,MYSQL |
+------------+
我如何得到这个,我需要查询...
答案 0 :(得分:6)
您要求分割功能,但您不必分割您的值以获得所需的结果。
此查询使用for xml
技巧在相关子查询中构建逗号分隔名称列表以连接值。它使用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
答案 1 :(得分:5)
首先,您最好的解决方案是不将数据存储在数据库中以逗号分隔的列表中。您应该考虑修复表结构。
如果无法更改表结构,则需要将列表中的数据拆分为行以指定正确的名称。分割数据后,您可以将数据连接回列表。
您可以在线找到许多不同的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;
要获得结果,我首先应用split
函数和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
此查询将以逗号分隔的列表分为以下内容:
| RN | NAME | ID |
--------------------
| 1 | MSSQL | 1 |
| 1 | Oracle | 3 |
| 2 | MySQl | 2 |
| 3 | MSSQL | 1 |
| 3 | MySQl | 2 |
使用正确的name
将数据放在多行后,您可以使用STUFF()
和FOR XML PATH
将其连接到列表中。您完整的查询将与此类似:
;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;
完整查询的结果是:
| DATABASENAME |
------------------
| MSSQL, Oracle |
| MySQl |
| MSSQL, MySQl |
答案 2 :(得分:0)
没有拆分,也没有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)
答案 3 :(得分:0)
--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