8

我的表 1 是:

T1

col1    col2
 C1     john
 C2     alex
 C3     piers
 C4     sara

所以表2:

T2

col1    col2
 R1     C1,C2,C4
 R2     C3,C4
 R3     C1,C4

如何产生这个?:

查询结果

col1      col2
 R1       john,alex,sara
 R2       piers,sara
 R3       john,sara

请帮我?

4

7 回答 7

23

理想情况下,您最好的解决方案是规范化 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

演示

然后,如果您想将数据显示为逗号分隔的列表,您可以使用FOR XML PATHand 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;

请参阅演示

如果您无法规范化数据,那么您可以做几件事。

首先,您可以创建一个拆分函数,将存储在列表中的数据转换为可以连接的行。拆分函数将类似于:

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;

当您使用拆分功能时,您可以将数据保留在多行中,也可以将值连接回逗号分隔的列表中:

;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

请参阅演示

获得结果的最后一种方法是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;

请参阅带有演示的 SQL Fiddle

于 2013-05-12T14:27:35.667 回答
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

SQL Fiddle 与演示

正如这个问题的其他地方所提到的,很难以任何有效的方式查询这种非规范化的数据,所以你的首要任务应该是调查更新表结构,但这至少可以得到你需要的结果.

于 2013-05-12T13:33:18.817 回答
3

如果您想在 oracle 中执行此任务,我们可以使用listagg并且可以轻松完成此任务。

SQL Server 中可用的可能等效项listaggStuff

因此,您可以尝试使用以下查询:

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
于 2013-05-12T12:53:17.757 回答
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       
于 2013-05-12T12:58:54.910 回答
1

如果你和我一样,并且你是 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 > ''
)
于 2017-10-05T08:11:17.337 回答
0

使用标准 SQL 无法解决此任务。在 Oracle 中,我会编写一个存储函数 (PL/SQL) 来解析 Name-ID-string (T2 col2) 并解析名称。不知道这在 Transact-SQL 中是否可行,但效率极低。

T2 是一个设计糟糕的、未规范化的表。那就是问题所在。如果您将其标准化,以便每个名称 ID 有一行(T2 中的第 2 列),您可以通过两个表的简单连接来获得名称列表。要生成所需的输出格式(逗号分隔),您需要编写除 SQL 之外的其他内容 - 可能是存储过程或其他迭代结果集的内容。

于 2013-05-12T12:39:06.907 回答
-1
Select T2.col1, group_concate(T1.col2)
From T1 join T2 on locate(T2.Col2, T1.Col1) > 0
group by T2.col1 
于 2021-07-12T16:56:27.137 回答