生成所有组合

时间:2020-08-17 08:27:07

标签: sql tsql

我有下表存储属性及其值。

DECLARE @temp table
(
    attribute varchar(10),
    value varchar(10)
)

INSERT INTO @temp
VALUES
('height', '1.6m'),
('height', '1.8m'),
('weight', '50kg'),
('weight', '80kg'),
('gender', 'male'),
('gender', 'female')

无论表中有多少个属性,我都希望生成这些属性的所有组合。

|Combination |Attribute |Value  |
|------------|----------|-------|
|1           |height    |1.6m   |
|1           |weight    |50kg   |
|1           |gender    |male   |
|2           |height    |1.6m   |
|2           |weight    |50kg   |
|2           |gender    |female |
|3           |height    |1.6m   |
|3           |weight    |80kg   |
|3           |gender    |male   |
|4           |height    |1.6m   |
|4           |weight    |80kg   |
|4           |gender    |female |
|5           |height    |1.8m   |
|5           |weight    |50kg   |
|5           |gender    |male   |
|6           |height    |1.8m   |
|6           |weight    |50kg   |
|6           |gender    |female |
|7           |height    |1.8m   |
|7           |weight    |80kg   |
|7           |gender    |male   |
|8           |height    |1.8m   |
|8           |weight    |80kg   |
|8           |gender    |female |

我认为CTE是必需的,但没有想到实现的方法。请帮忙。

6 个答案:

答案 0 :(得分:2)

以下查询似乎有效,是的,它使用CTE:

WITH cte AS (
    SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) rn
    FROM (SELECT attribute AS attr1, value AS val1 FROM temp WHERE attribute = 'height') t1
    CROSS JOIN (SELECT attribute AS attr2, value AS val2 FROM temp WHERE attribute = 'weight') t2
    CROSS JOIN (SELECT attribute AS attr3, value AS val3 FROM temp WHERE attribute = 'gender') t3
)

SELECT rn AS Combination, attr1 AS Attribute, val1 AS Value FROM cte WHERE attr1 = 'height' UNION ALL
SELECT rn, attr2, val2 FROM cte WHERE attr2 = 'weight' UNION ALL
SELECT rn, attr3, val3 FROM cte WHERE attr3 = 'gender'
ORDER BY Combination, Attribute, Value;

screen capture from demo link below

Demo

此方法使用一系列交叉联接来生成中间表(CTE),该中间表包含每个组合的一条记录。有8种组合,给定3个属性,每个属性2个值。然后,我们使用一系列并集将其解开为所需的长格式结果。

答案 1 :(得分:2)

with cte as
 (
   select
      -- combination number
      row_number() over (order by (select null)) as rn
     ,t1.attribute as a1
     ,t1.value     as v1
     ,t2.attribute as a2
     ,t2.value     as v2
     ,t3.attribute as a3
     ,t3.value     as v3
   -- create all combinations, each attribute needs an extra join
   from @temp as t1
   join @temp as t2 on t1.attribute < t2.attribute
   join @temp as t3 on t2.attribute < t3.attribute
 )
select c.*
from cte
-- unpivot using CROSS APPLY 
cross apply
(
  values
    (rn, a1, v1),
    (rn, a2, v2),
    (rn, a3, v3)
) c (combination, attribute, value)
order by 1,2,3;

每个其他属性都需要一个额外的联接,“选择”列表中的两列和“交叉应用”中的“值”。

请参见fiddle

当然可以使用基于select distinct attribute的Dynamic SQL创建此查询

答案 2 :(得分:2)

如果我理解正确,则您不想对表中的属性数量进行预编程。您只需要所有组合,不管有多少。

这表示递归CTE。我认为以字符串形式获取组合是最简单的-将所有行合并为一行。为此,如果id中有一个temp列,这将很有帮助。所以,我假设一个存在。

因此,要获取ID的分组:

with t as (
      select t.*, 
             dense_rank() over (order by attribute) as attribute_seqnum
      from temp t
     ),
     cte as (
      select t.attribute, t.value, attribute_seqnum, 1 as lev, convert(varchar(max), t.id) as ids
      from t 
      where attribute_seqnum = 1
      union all
      select t.attribute, t.value, t.attribute_seqnum, lev + 1, concat(ids, ',', t.id)
      from cte join
           t
           on t.attribute_seqnum = cte.attribute_seqnum + 1

     )
select *
from (select cte.*, max(lev) over () as max_lev
      from cte
     ) x
where lev = max_lev;

这是通过枚举temp中的属性,然后一次添加一个来实现的。

您可以取消拆分ID,然后重新加入表格以获取单独的行:

with t as (
      select t.*, 
             dense_rank() over (order by attribute) as attribute_seqnum
      from temp t
     ),
     cte as (
      select t.attribute, t.value, attribute_seqnum, 1 as lev, convert(varchar(max), t.id) as ids
      from t 
      where attribute_seqnum = 1
      union all
      select t.attribute, t.value, t.attribute_seqnum, lev + 1, concat(ids, ',', t.id)
      from cte join
           t
           on t.attribute_seqnum = cte.attribute_seqnum + 1

     )
select x.seqnum, t.attribute, t.value
from (select row_number() over (order by (select null)) as seqnum, x.*
      from (select cte.*, max(lev) over () as max_lev
            from cte
           ) x
      where lev = max_lev
     ) x cross apply
     string_split(ids, ',') s join
     temp t
     on t.id = convert(int, s.value);

Here是db <>小提琴。

注意:您不必在id中有temp才能起作用。您可以在递归的每个步骤中存储属性/值对。但是,我认为这只会增加一些其他的复杂性。

答案 3 :(得分:1)

您可以结合使用cross joinunpivot来实现这一目标。

交叉加入

select  row_number() over(order by t_h.attribute) as 'combo',
        t_h.*, t_w.*, t_g.*
from @temp t_h
cross join @temp t_w
cross join @temp t_g
where t_h.attribute = 'height'
  and t_w.attribute = 'weight'
  and t_g.attribute = 'gender';


combo                attribute  value      attribute  value      attribute  value
-------------------- ---------- ---------- ---------- ---------- ---------- ----------
1                    height     1.6m       weight     50kg       gender     male
2                    height     1.6m       weight     50kg       gender     female
3                    height     1.8m       weight     50kg       gender     male
4                    height     1.8m       weight     50kg       gender     female
5                    height     1.6m       weight     80kg       gender     male
6                    height     1.6m       weight     80kg       gender     female
7                    height     1.8m       weight     80kg       gender     male
8                    height     1.8m       weight     80kg       gender     female

取消枢纽

我将上一个查询移至CTE,但也可以进行子查询。

with cte_source as (
    select  row_number() over(order by t_h.attribute) as 'combo',
            t_h.attribute as 'att_h',
            t_h.value as 'val_h',
            t_w.attribute as 'att_w',
            t_w.value as 'val_w',
            t_g.attribute as 'att_g',
            t_g.value as 'val_g'
    from @temp t_h
    cross join @temp t_w
    cross join @temp t_g
    where t_h.attribute = 'height'
      and t_w.attribute = 'weight'
      and t_g.attribute = 'gender'
)
select  up.combo,
        case up.x
            when 'val_h' then 'height'
            when 'val_w' then 'weight'
            when 'val_g' then 'gender'
        end as 'attribute',
        up.val as 'value'
from cte_source s
unpivot (val for x in (val_h, val_w, val_g)) up;


combo                attribute value
-------------------- --------- ----------
1                    height    1.6m
1                    weight    50kg
1                    gender    male
2                    height    1.6m
2                    weight    50kg
2                    gender    female
3                    height    1.8m
3                    weight    50kg
3                    gender    male
4                    height    1.8m
4                    weight    50kg
4                    gender    female
5                    height    1.6m
5                    weight    80kg
5                    gender    male
6                    height    1.6m
6                    weight    80kg
6                    gender    female
7                    height    1.8m
7                    weight    80kg
7                    gender    male
8                    height    1.8m
8                    weight    80kg
8                    gender    female

答案 4 :(得分:0)

这是我尝试的一种简单解决方案

;with
rows_cte([name], attribute, rn) as (
    select v.*, row_number() over (order by (select null)) from 
    (select [value] h, attribute a from @temp where attribute='height') h
    cross join
    (select [value] w, attribute a from @temp where attribute='weight') w
    cross join
    (select [value] g, attribute a from @temp where attribute='gender') g
    cross apply 
    (values (h, h.a), (w, w.a), (g, g.a)) v(n, a))
select ((rn-1)/3)+1 combination, rc.attribute, rc.[name]
from rows_cte rc
order by combination;

答案 5 :(得分:-1)

也许是:

select t1.attribute, t2.value from tab t1, tab t2

或者如果您需要唯一的内容:

select t1.attribute, t2.value
from 
(select distinct attribute from tab) t1,
(select distinct value from tab ) t2