在逗号分隔的列表中查找标识符的唯一组合

时间:2012-11-21 18:33:31

标签: sql sql-server tsql permutation

我有一个复杂的查询,其结果我暂时存储在一个临时表中,以简化起见。我有一个逗号=分隔的三个字符唯一标识符组合的列表,如下面的屏幕截图:

enter image description here

您可以在第2行和第3行中看到3个字符标识符相同,但顺序相反。计数是不同的,但这些值是正确的(如果X和Y的人口规模不相等,则具有X和Y的人数与具有Y和X的人数不同)。我想找到alldrugs的独特组合,无论它们出现的顺序如何。我想象一个像row_number()这样的分区,它给我们1和2代表第2行和第3行。

我从未在SQL中尝试过这样的事情,但我的想法是

select *,
    case when LEN(alldrugs)-LEN(replace(alldrugs,',',''))= 1 then 2 
         when LEN(alldrugs)-LEN(REPLACE(alldrugs,',',''))= 2 then 3
         when LEN(alldrugs)-LEN(REPLACE(alldrugs,',',''))= 3 then 4
         when LEN(alldrugs)-LEN(REPLACE(alldrugs,',',''))= 4 then 5
         when LEN(alldrugs)-LEN(REPLACE(alldrugs,',',''))= 5 then 6
         else 1 end as numDrugs
    from #testfix as tf
    order by alldrugs,numDrugs 

由于列表是逗号分隔的,case when语句会查找一行中的逗号数,并给出要查找的3位数跨度的数量。对于含有2种药物(一个逗号)的alldrugs列,我可以将结果作为CTE,在CTE上自行加入并检查是否right(alldrugs,3) = left(alldrugs,3)。这显然不可扩展。是否有一种惯用的方式来获得这样的独特组合?

2 个答案:

答案 0 :(得分:2)

确定。这是一个想法,假设您有一个所有可能的3个字母代码的列表。我们的想法是扩展列表,因此每行有一行,然后重新组合结果。在另一个数据库中,您可以使用group_concatlistagg。对于SQL Server,我们必须使用set functions。

要展开列表:

with fulllist as (
    select yt.*, c.code, row_number() over (order by (select NULL)) as id
    from YourTable yt join
         Codes c
         on ','+yt.AllDrugs+',' like '%,'+c.code+',%'
   )

接下来,自联接是识别集合何时相同的一种方法。如果两组(“id”)具有相同数量的药物并且全部匹配,那么它们是相同的。因此,对于原始表中的每一行,我们将找到具有相同药物的最小行。这成为分组目的的id。

以下(未经测试的)查询实现了这个:

with fulllist as (
    select yt.*, c.code
    from YourTable yt join
         Codes c
         on ','+yt.AllDrugs+',' like '%,'+c.code+',%'
   ),
   Pairs as (
    select id1, min(id2) as minId
    from (select fl1.id as id1, fl2.id as id2
          from (select fl.*, count(*) over (partition by yt.id) as NumCodes
                from fulllist fl
               ) fl1 join
               (select fl.* count(*) over (partition by yt.id
                from fulllist fl
               ) fl2
               on fl1.code = fl2.code and
                  fl1.NumCodes = fl2.NumCodes
          group by fl1.id, fl2.id
          having count(*) as fl1.NumCodes
         ) t
     group by id1
    )
select p.minId, min(fl.AllDrugs), sum(fl.DrugFamilyCounts)
from FullList fl join
     Pairs p
     on fl.id = p.minId
group by p.minId
order by 2 desc

答案 1 :(得分:1)

假设您使用的是SQL Server 2008+,则可以使用自定义split函数和STUFF函数的组合将列表拆分并重新组合为排序顺序。然后,您可以在重新排列的列表中选择不同的,以获得唯一的组合。

这是一个应该有效的简单split函数(source):

CREATE FUNCTION dbo.Split
(
    @RowData nvarchar(2000),
    @SplitOn nvarchar(5)
)  
RETURNS @RtnValue table 
(
    Id int identity(1,1),
    Data nvarchar(100)
) 
AS  
BEGIN 
    Declare @Cnt int
    Set @Cnt = 1

    While (Charindex(@SplitOn,@RowData)>0)
    Begin
        Insert Into @RtnValue (data)
        Select 
            Data = ltrim(rtrim(Substring(@RowData,1,Charindex(@SplitOn,@RowData)-1)))

        Set @RowData = Substring(@RowData,Charindex(@SplitOn,@RowData)+1,len(@RowData))
        Set @Cnt = @Cnt + 1
    End

    Insert Into @RtnValue (data)
    Select Data = ltrim(rtrim(@RowData))

    Return
END

然后这是一个获取不同列表的查询:

select
  distinct
  STUFF((select ',' + data as [text()]
         from dbo.split(tf.alldrugs, ',')
         order by data
         FOR XML PATH('')) , 1 , 1 , '' ) as alldrugsordered

from
  TestFix tf

演示:http://www.sqlfiddle.com/#!3/d890b/4

示例输出:

| ALLDRUGSORDERED |
-------------------
|         H2F,H3A |
|             H2S |
|             H3A |
|         H3A,H4B |
|         H3A,H6H |
|             H4B |
|             H6H |
|             J7C |