我的table1是:
col1 col2
C1 john
C2 alex
C3 piers
C4 sara
所以表2:
col1 col2
R1 C1,C2,C4
R2 C3,C4
R3 C1,C4
如何结果?:
col1 col2
R1 john,alex,sara
R2 piers,sara
R3 john,sara
请帮帮我?
答案 0 :(得分:21)
理想情况下,您最好的解决方案是规范化Table2,这样您就不会存储以逗号分隔的列表。
一旦您将此数据规范化,您就可以轻松查询数据。新表结构可能与此类似:
CREATE TABLE T1
(
[col1] varchar(2),
[col2] varchar(5),
constraint pk1_t1 primary key (col1)
);
INSERT INTO T1
([col1], [col2])
VALUES
('C1', 'john'),
('C2', 'alex'),
('C3', 'piers'),
('C4', 'sara')
;
CREATE TABLE T2
(
[col1] varchar(2),
[col2] varchar(2),
constraint pk1_t2 primary key (col1, col2),
constraint fk1_col2 foreign key (col2) references t1 (col1)
);
INSERT INTO T2
([col1], [col2])
VALUES
('R1', 'C1'),
('R1', 'C2'),
('R1', 'C4'),
('R2', 'C3'),
('R2', 'C4'),
('R3', 'C1'),
('R3', 'C4')
;
对表进行规范化将使您更容易通过连接表来查询数据:
select t2.col1, t1.col2
from t2
inner join t1
on t2.col2 = t1.col1
请参阅Demo
然后,如果您想将数据显示为以逗号分隔的列表,则可以使用FOR XML PATH
和STUFF
:
select distinct t2.col1,
STUFF(
(SELECT distinct ', ' + t1.col2
FROM t1
inner join t2 t
on t1.col1 = t.col2
where t2.col1 = t.col1
FOR XML PATH ('')), 1, 1, '') col2
from t2;
请参阅Demo。
如果您无法规范化数据,那么您可以执行以下操作。
首先,您可以创建一个拆分函数,将存储在列表中的数据转换为可以连接的行。拆分功能与此类似:
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,function时,您可以将数据保留在多行中,也可以将值连接回逗号分隔的列表中:
;with cte as
(
select c.col1, t1.col2
from t1
inner join
(
select t2.col1, i.items col2
from t2
cross apply dbo.split(t2.col2, ',') i
) c
on t1.col1 = c.col2
)
select distinct c.col1,
STUFF(
(SELECT distinct ', ' + c1.col2
FROM cte c1
where c.col1 = c1.col1
FOR XML PATH ('')), 1, 1, '') col2
from cte c
请参阅Demo。
获得结果的最后一种方法是直接应用FOR XML PATH
。
select col1,
(
select ', '+t1.col2
from t1
where ','+t2.col2+',' like '%,'+cast(t1.col1 as varchar(10))+',%'
for xml path(''), type
).value('substring(text()[1], 3)', 'varchar(max)') as col2
from t2;
答案 1 :(得分:4)
以下是一种在没有函数的情况下拆分数据的方法,然后使用标准XML PATH
方法获取CSV列表:
with CTE as
(
select T2.col1
, T1.col2
from T2
inner join T1 on charindex(',' + T1.col1 + ',', ',' + T2.col2 + ',') > 0
)
select T2.col1
, col2 = stuff(
(
select ',' + CTE.col2
from CTE
where T2.col1 = CTE.col1
for xml path('')
)
, 1
, 1
, ''
)
from T2
正如在这个问题的其他地方已经提到的那样,很难以任何有效的方式查询这种非规范化数据,因此您的首要任务应该是调查更新表结构,但这至少可以让你需要的结果。
答案 2 :(得分:2)
如果您想在oracle中执行此任务,我们可以使用listagg
并轻松完成此任务。
我在SQL SERVER中表现不佳,但我在Sqlserver中为listagg搜索了可能等效的avaialble,我得到了同样的函数Stuff
- Check this
所以使用东西你可以尝试使用以下查询 -
SELECT T2.Col1,
Stuff((SELECT ',' + CAST(T1.Col2 AS VARCHAR(100))
FROM T1
WHERE T2.Col2 LIKE T1.Col1
FOR Xml Path('')),
1,
1,
'')
FROM T2
答案 3 :(得分:1)
首先在tbl2上为split col2写一个表值函数。
CREATE FUNCTION [dbo].[Split](@String varchar(100), @Delimiter char(1))
returns @temptable TABLE (items VARCHAR(5))
as
begin
declare @idx int
declare @slice VARCHAR(5)
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
Go
;WITH SplitList
AS ( SELECT T2.Col1 ,
T1.Col2
FROM T2
CROSS APPLY dbo.Split(T2.Col2, ',') S
INNER JOIN T1 ON T1.Col1 = S.Items
)
SELECT T2.Col1 ,
STUFF(( SELECT ', ' + SplitList.Col2
FROM SplitList
WHERE SplitList.Col1 = T2.Col1
FOR
XML PATH('')
), 1, 2, '')
FROM T2
答案 4 :(得分:0)
使用标准SQL无法解决此任务。在Oracle中,我会编写一个存储函数(PL / SQL)来解析Name-ID-string(T2 col2)并解析名称。不知道在Transact-SQL中是否可行,但它的效率很低。
T2是一个设计糟糕,没有规范化的表格。那就是问题所在。 如果你将它标准化,那么每个Name-ID有一行(T2中的col 2),你可以通过两个表的简单连接获得名称列表。 要生成所需的输出格式(逗号分隔),您需要编写除SQL之外的其他内容 - 可能是存储过程或其他迭代结果集的内容。
答案 5 :(得分:0)
如果你像我一样,并且你是CTE的坚持者,特别是递归CTE应该是STUFF和XML Path:
DECLARE @T1 TABLE (
col1 CHAR(2),
col2 VARCHAR(10)
)
INSERT INTO @T1
VALUES ('C1', 'john'),
('C2', 'alex'),
('C3', 'piers'),
('C4', 'sara');
DECLARE @T2 TABLE (
col1 CHAR(2),
col2 CHAR(100)
)
INSERT INTO @T2
VALUES ('R1', 'C1,C2,C4'),
('R2', 'C3,C4'),
('R3', 'C1,C4');
WITH T2Sorted AS (
SELECT col1, col2, RN = ROW_NUMBER() OVER (ORDER BY col1) FROM @T2
), CTERecursionOnT2 AS (
SELECT RN, col1, col2, 0 AS PrevCharIndex, CHARINDEX(',', col2, 1) AS NextCharIndex FROM T2Sorted
UNION ALL
SELECT a.RN, a.col1, a.col2, b.NextCharIndex, CHARINDEX(',', a.col2, b.NextCharIndex + 1)
FROM T2Sorted a
JOIN CTERecursionOnT2 b ON a.RN = b.RN
WHERE b.NextCharIndex > 0
), CTEIndividualCol2Items AS (
SELECT *, SUBSTRING(col2, PrevCharIndex + 1, CASE WHEN NextCharIndex = 0 THEN LEN(col2) ELSE NextCharIndex - 1 END - PrevCharIndex) AS itemCol2
FROM CTERecursionOnT2
), CTELookupT1 AS (
SELECT a.col1, b.col2, RN = ROW_NUMBER() OVER (PARTITION BY a.col1 ORDER BY a.PrevCharIndex)
FROM CTEIndividualCol2Items a
JOIN @T1 b ON a.itemCol2 = b.col1
), CTERecursionOnLookupT1 AS (
SELECT col1, CAST(col2 AS VARCHAR(MAX)) AS col2, RN
FROM CTELookupT1
WHERE RN = 1
UNION ALL
SELECT a.col1, b.col2 + ',' + a.col2, a.RN
FROM CTELookupT1 a
JOIN CTERecursionOnLookupT1 b ON a.col1 = b.col1 AND a.RN = b.RN + 1
), CTEFinal AS (
SELECT *, RNDesc = ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY RN DESC)
FROM CTERecursionOnLookupT1
)
SELECT col1, col2
FROM CTEFinal
WHERE RNDesc = 1
ORDER BY col1
显然你可以将第一个递归部分拆分成单独的函数,因为已经商定的解决方案建议即CTERecursionOnT2,因此CTEIndividualCol2Items可以作为您的替代拆分函数(我也会包括订单ID),因此:
;WITH CTEIndividualCol2Items AS (
SELECT a.col1, b.value as itemCol2, b.id AS PrevCharIndex
FROM @T2 a
CROSS APPLY (
SELECT id, items FROM dbo.Split(a.col2, ',')
) b
) ...
你拆分功能:
CREATE FUNCTION dbo.Split(@String varchar(100), @Delimiter char(1))
RETURNS TABLE
AS
RETURN
(
WITH CTERecursion AS (
SELECT id = 1, PrevCharIndex = 0, NextCharIndex = CHARINDEX(@Delimiter, @String, 1)
UNION ALL
SELECT id + 1, NextCharIndex, CHARINDEX(@Delimiter, @String, NextCharIndex + 1) FROM CTERecursion WHERE NextCharIndex > 0
)
SELECT Id, items = SUBSTRING(@String, PrevCharindex + 1, (CASE WHEN NextCharIndex = 0 THEN LEN(@String) ELSE NextCharIndex - 1 END) - PrevCharIndex)
FROM CTERecursion
WHERE @String > ''
)
答案 6 :(得分:-1)
Select T2.col1, group_concate(T1.col2)
From T1 join T2 on locate(T2.Col2, T1.Col1) > 0
group by T2.col1