代码之家  ›  专栏  ›  技术社区  ›  David

在SQL中生成所有组合

  •  16
  • David  · 技术社区  · 14 年前

    我需要生成所有大小的组合 @k 在给定的大小集合中 @n . 有人能检查一下下面的SQL,首先确定下面的逻辑是否返回了预期的结果,然后确定是否有更好的方法吗?

    /*CREATE FUNCTION dbo.Factorial ( @x int ) 
    RETURNS int 
    AS
    BEGIN
        DECLARE @value int
    
        IF @x <= 1
            SET @value = 1
        ELSE
            SET @value = @x * dbo.Factorial( @x - 1 )
    
        RETURN @value
    END
    GO*/
    SET NOCOUNT ON;
    DECLARE @k int = 5, @n int;
    DECLARE @set table ( [value] varchar(24) );
    DECLARE @com table ( [index] int );
    
    INSERT @set VALUES ('1'),('2'),('3'),('4'),('5'),('6');
    
    SELECT @n = COUNT(*) FROM @set;
    
    DECLARE @combinations int = dbo.Factorial(@n) / (dbo.Factorial(@k) * dbo.Factorial(@n - @k));
    
    PRINT CAST(@combinations as varchar(max)) + ' combinations';
    
    DECLARE @index int = 1;
    
    WHILE @index <= @combinations
    BEGIN
        INSERT @com VALUES (@index)
        SET @index = @index + 1
    END;
    
    WITH [set] as (
        SELECT 
            [value], 
            ROW_NUMBER() OVER ( ORDER BY [value] ) as [index]
        FROM @set
    )
    SELECT 
        [values].[value], 
        [index].[index] as [combination]
    FROM [set] [values]
    CROSS JOIN @com [index]
    WHERE ([index].[index] + [values].[index] - 1) % (@n) BETWEEN 1 AND @k
    ORDER BY
        [index].[index];
    
    5 回复  |  直到 13 年前
        1
  •  64
  •   Community Stefan Steinegger    4 年前

    返回组合

    WITH Nums (Num) AS (
       SELECT Num
       FROM Numbers
       WHERE Num BETWEEN 0 AND POWER(2, @n) - 1
    ), BaseSet AS (
       SELECT ind = Power(2, Row_Number() OVER (ORDER BY Value) - 1), *
       FROM @set
    ), Combos AS (
       SELECT
          ComboID = N.Num,
          S.Value,
          Cnt = Count(*) OVER (PARTITION BY N.Num)
       FROM
          Nums N
          INNER JOIN BaseSet S ON N.Num & S.ind <> 0
    )
    SELECT
       ComboID,
       Value
    FROM Combos
    WHERE Cnt = @k
    ORDER BY ComboID, Value;
    

    这个查询执行得很好,但是我想到了一种优化它的方法,从 Nifty Parallel Bit Count

    WITH Nums AS (
       SELECT Num, P1 = (Num & 0x55555555) + ((Num / 2) & 0x55555555)
       FROM dbo.Numbers
       WHERE Num BETWEEN 0 AND POWER(2, @n) - 1
    ), Nums2 AS (
       SELECT Num, P2 = (P1 & 0x33333333) + ((P1 / 4) & 0x33333333)
       FROM Nums
    ), Nums3 AS (
       SELECT Num, P3 = (P2 & 0x0f0f0f0f) + ((P2 / 16) & 0x0f0f0f0f)
       FROM Nums2
    ), BaseSet AS (
       SELECT ind = Power(2, Row_Number() OVER (ORDER BY Value) - 1), *
       FROM @set
    )
    SELECT
       ComboID = N.Num,
       S.Value
    FROM
       Nums3 N
       INNER JOIN BaseSet S ON N.Num & S.ind <> 0
    WHERE P3 % 255 = @k
    ORDER BY ComboID, Value;
    

    我去看了位计数页,认为如果我不做%255,而是一直用位运算,这个性能会更好。当我有机会的时候,我会尝试一下,看看它有多好。

    我的性能声明是基于在没有orderby子句的情况下运行的查询。为清楚起见,这段代码所做的是计算中设置的1位的数量 Num Numbers

    作为记录,这种使用整数的位模式来选择集合成员的技术就是我创造的“垂直交叉连接”。它有效地导致多组数据的交叉连接,其中集合和交叉连接的数量是任意的。这里,集合的数量是一次获取的项目数。

    实际上,通常水平意义上的交叉连接(即使用每个连接将更多列添加到现有列列表中)如下所示:

    SELECT
       A.Value,
       B.Value,
       C.Value
    FROM
       @Set A
       CROSS JOIN @Set B
       CROSS JOIN @Set C
    WHERE
       A.Value = 'A'
       AND B.Value = 'B'
       AND C.Value = 'C'
    

    我上面的查询有效地“交叉连接”了很多次,只需要一个连接。当然,与实际的交叉连接相比,结果是不精确的,但这只是一个小问题。

    ALTER FUNCTION dbo.Factorial (
       @x bigint
    )
    RETURNS bigint
    AS
    BEGIN
       IF @x <= 1 RETURN 1
       RETURN @x * dbo.Factorial(@x - 1)
    END
    

    现在你可以计算更大的组合,而且效率更高。你甚至可以考虑使用 decimal(38, 0)

    其次,给定的查询没有返回正确的结果。例如,使用下面性能测试中的测试数据,set 1与set 18相同。看起来您的查询采用了一个环绕的滑动条带:每个集合总是5个相邻的成员,看起来是这样的(我旋转以使其更易于查看):

     1 ABCDE            
     2 ABCD            Q
     3 ABC            PQ
     4 AB            OPQ
     5 A            NOPQ
     6             MNOPQ
     7            LMNOP 
     8           KLMNO  
     9          JKLMN   
    10         IJKLM    
    11        HIJKL     
    12       GHIJK      
    13      FGHIJ       
    14     EFGHI        
    15    DEFGH         
    16   CDEFG          
    17  BCDEF           
    18 ABCDE            
    19 ABCD            Q
    

     31 ABCDE  
     47 ABCD F 
     55 ABC EF 
     59 AB DEF 
     61 A CDEF 
     62  BCDEF 
     79 ABCD  G
     87 ABC E G
     91 AB DE G
     93 A CDE G
     94  BCDE G
    103 ABC  FG
    107 AB D FG
    109 A CD FG
    110  BCD FG
    115 AB  EFG
    117 A C EFG
    118  BC EFG
    121 A  DEFG
    ...
    

    为了让感兴趣的人了解位模式->组合事物索引,请注意二进制中的31=11111,模式是ABCDE。二进制中的121是1111001,模式是\uu DEFG(向后映射)。

    实数表的性能结果

    我在上面的第二个查询中用大集合进行了一些性能测试。我目前没有所用服务器版本的记录。以下是我的测试数据:

    DECLARE
       @k int,
       @n int;
    
    DECLARE @set TABLE (value varchar(24));
    INSERT @set VALUES ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P'),('Q');
    SET @n = @@RowCount;
    SET @k = 5;
    
    DECLARE @combinations bigint = dbo.Factorial(@n) / (dbo.Factorial(@k) * dbo.Factorial(@n - @k));
    SELECT CAST(@combinations as varchar(max)) + ' combinations', MaxNumUsedFromNumbersTable = POWER(2, @n);
    

    下面的第二组数字是除以表中第一行的系数,只是为了说明它是如何缩放的。

    埃里克

    Items  CPU   Writes  Reads  Duration |  CPU  Writes  Reads Duration
    ----- ------ ------ ------- -------- | ----- ------ ------ --------
    17•5    7344     0     3861    8531  |
    18•9   17141     0     7748   18536  |   2.3          2.0      2.2
    20•10  76657     0    34078   84614  |  10.4          8.8      9.9
    21•11 163859     0    73426  176969  |  22.3         19.0     20.7
    21•20 142172     0    71198  154441  |  19.4         18.4     18.1
    

    Items  CPU   Writes  Reads  Duration |  CPU  Writes  Reads Duration
    ----- ------ ------ ------- -------- | ----- ------ ------ --------
    17•5     422    70    10263     794  | 
    18•9    6046   980   219180   11148  |  14.3   14.0   21.4    14.0
    20•10  24422  4126   901172   46106  |  57.9   58.9   87.8    58.1
    21•11  58266  8560  2295116  104210  | 138.1  122.3  223.6   131.3
    21•20  51391     5  6291273   55169  | 121.8    0.1  613.0    69.5
    

    在意识到我的解决方案在使用动态数字表时会表现得更好之前,我最初做了一个评论:

    我喜欢这种问题的单查询解决方案,但是如果您希望获得最佳性能,那么实际的交叉连接是最好的,除非您开始处理大量的组合。但是,有人想要几十万甚至数百万排的队伍做什么呢?即使是不断增长的阅读量似乎也不是太大的问题,尽管600万是一个很大的数字,而且它正在迅速变大。。。

    不管怎样。动态SQL获胜。我还有一个漂亮的问题。:)

    动态数字表的性能结果

    当我最初写这个答案时,我说:

    on-the-fly numbers table ,但我没试过。

    嗯,我试过了,结果是它的表现好多了!以下是我使用的查询:

    DECLARE @N int = 16, @K int = 12;
    
    CREATE TABLE #Set (Value char(1) PRIMARY KEY CLUSTERED);
    CREATE TABLE #Items (Num int);
    INSERT #Items VALUES (@K);
    INSERT #Set 
    SELECT TOP (@N) V
    FROM
       (VALUES ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P'),('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('Z')) X (V);
    GO
    DECLARE
       @N int = (SELECT Count(*) FROM #Set),
       @K int = (SELECT TOP 1 Num FROM #Items);
    
    DECLARE @combination int, @value char(1);
    WITH L0 AS (SELECT 1 N UNION ALL SELECT 1),
    L1 AS (SELECT 1 N FROM L0, L0 B),
    L2 AS (SELECT 1 N FROM L1, L1 B),
    L3 AS (SELECT 1 N FROM L2, L2 B),
    L4 AS (SELECT 1 N FROM L3, L3 B),
    L5 AS (SELECT 1 N FROM L4, L4 B),
    Nums AS (SELECT Row_Number() OVER(ORDER BY (SELECT 1)) Num FROM L5),
    Nums1 AS (
       SELECT Num, P1 = (Num & 0x55555555) + ((Num / 2) & 0x55555555)
       FROM Nums
       WHERE Num BETWEEN 0 AND Power(2, @N) - 1
    ), Nums2 AS (
       SELECT Num, P2 = (P1 & 0x33333333) + ((P1 / 4) & 0x33333333)
       FROM Nums1
    ), Nums3 AS (
       SELECT Num, P3 = (P2 & 0x0F0F0F0F) + ((P2 / 16) & 0x0F0F0F0F)
       FROM Nums2
    ), BaseSet AS (
       SELECT Ind = Power(2, Row_Number() OVER (ORDER BY Value) - 1), *
       FROM #Set
    )
    SELECT
       @Combination = N.Num,
       @Value = S.Value
    FROM
       Nums3 N
       INNER JOIN BaseSet S
          ON N.Num & S.Ind <> 0
    WHERE P3 % 255 = @K;
    

    Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) Standard Edition on Windows NT 5.2 <X86> (Build 3790: Service Pack 2) (VM) 在虚拟机上运行。

    下面的图表显示了N和K值到21的性能曲线。它们的基础数据在 another answer on this page . 这些值是在每个K和N值处对每个查询运行5次的结果,然后抛出每个度量的最佳值和最差值,并对剩余的3次进行平均。

    基本上,我的版本在高值N和低值K处有一个“肩部”(在图表的最左角),这使得它的性能比动态SQL版本差。但是,这保持了相当低的值和常数,并且N=21和K=11附近的中心峰值在持续时间、CPU和读取方面比动态SQL版本低得多。

    Duration Chart CPU Chart Reads Chart Row Counts Chart

    请看 my additional answer on this page 完整的性能结果。我达到了后字符限制,不能在这里包括它。(还有其他的想法吗?)为了对比我的第一个版本的性能结果,这里的格式与之前相同:

    Items  CPU  Duration  Reads  Writes |  CPU  Duration  Reads 
    ----- ----- -------- ------- ------ | ----- -------- -------
    17•5    354      378   12382      0 |
    18•9   1849     1893   97246      0 |   5.2      5.0     7.9
    20•10  7119     7357  369518      0 |  20.1     19.5    29.8
    21•11 13531    13807  705438      0 |  38.2     36.5    57.0
    21•20  3234     3295     48       0 |   9.1      8.7     0.0
    

    彼得

    Items  CPU  Duration  Reads  Writes |  CPU  Duration  Reads 
    ----- ----- -------- ------- ------ | ----- -------- -------
    17•5     41       45    6433      0 | 
    18•9   2051     1522  214021      0 |  50.0     33.8    33.3
    20•10  8271     6685  864455      0 | 201.7    148.6   134.4
    21•11 18823    15502 2097909      0 | 459.1    344.5   326.1
    21•20 25688    17653 4195863      0 | 626.5    392.3   652.2
    

    结论

    • 我最初的测试不够广泛,无法真正显示这两个版本的性能特征。

    工期分析

    • 从1412开始,我的版本快了65倍(59>100ms),动态SQL版本快了64倍(60>100ms)。然而,在我的版本快的时候,它节省了256.5秒的总平均持续时间,而当动态SQL版本快的时候,它节省了80.2秒。
    • 所有试验的总平均持续时间为Erik 270.3秒,Peter 446.2秒。
    • 如果创建一个查找表来确定要使用哪个版本(为输入选择更快的版本),那么所有结果都可以在188.7秒内执行。每次使用最慢的一个需要527.7秒。

    阅读分析

    • 在每次读取9个项目之前,不同版本的读取量没有显著差异(>1000)。到目前为止,我的版本赢了30次,动态SQL版本赢了17次。
    • 所有试验的总平均阅读量为埃里克840万,彼得840万。
    • 如果创建一个查找表来确定要使用哪个版本(为输入选择最佳版本),那么所有结果都可以在8M次读取中执行。每次使用最差的一个需要8430万次阅读。

    我非常希望看到一个更新的动态SQL版本的结果,这个版本为我上面描述的每个步骤中选择的项设置了额外的上限。

    附录

    Convert(int) 关于 row_number()

    WITH L0 AS (SELECT 1 N UNION ALL SELECT 1),
    L1 AS (SELECT 1 N FROM L0, L0 B),
    L2 AS (SELECT 1 N FROM L1, L1 B),
    L3 AS (SELECT 1 N FROM L2, L2 B),
    L4 AS (SELECT 1 N FROM L3, L3 B),
    L5 AS (SELECT 1 N FROM L4, L4 B),
    Nums AS (SELECT Row_Number() OVER(ORDER BY (SELECT 1)) Num FROM L5),
    Nums1 AS (
       SELECT Convert(int, Num) Num
       FROM Nums
       WHERE Num BETWEEN 1 AND Power(2, @N) - 1
    ), Nums2 AS (
       SELECT
          Num,
          P1 = Num - ((Num / 2) & 0xDB6DB6DB) - ((Num / 4) & 0x49249249)
       FROM Nums1
    ),
    Nums3 AS (SELECT Num, Bits = ((P1 + P1 / 8) & 0xC71C71C7) % 63 FROM Nums2),
    BaseSet AS (SELECT Ind = Power(2, Row_Number() OVER (ORDER BY Value) - 1), * FROM #Set)
    SELECT
       N.Num,
       S.Value
    FROM
       Nums3 N
       INNER JOIN BaseSet S
          ON N.Num & S.Ind <> 0
    WHERE
       Bits = @K
    

    我忍不住又展示了一个版本,它通过查找来获取位的计数。它甚至可能比其他版本更快:

    DECLARE @BitCounts binary(255) = 
       0x01010201020203010202030203030401020203020303040203030403040405
       + 0x0102020302030304020303040304040502030304030404050304040504050506
       + 0x0102020302030304020303040304040502030304030404050304040504050506
       + 0x0203030403040405030404050405050603040405040505060405050605060607
       + 0x0102020302030304020303040304040502030304030404050304040504050506
       + 0x0203030403040405030404050405050603040405040505060405050605060607
       + 0x0203030403040405030404050405050603040405040505060405050605060607
       + 0x0304040504050506040505060506060704050506050606070506060706070708;
    WITH L0 AS (SELECT 1 N UNION ALL SELECT 1),
    L1 AS (SELECT 1 N FROM L0, L0 B),
    L2 AS (SELECT 1 N FROM L1, L1 B),
    L3 AS (SELECT 1 N FROM L2, L2 B),
    L4 AS (SELECT 1 N FROM L3, L3 B),
    L5 AS (SELECT 1 N FROM L4, L4 B),
    Nums AS (SELECT Row_Number() OVER(ORDER BY (SELECT 1)) Num FROM L5),
    Nums1 AS (SELECT Convert(int, Num) Num FROM Nums WHERE Num BETWEEN 1 AND Power(2, @N) - 1),
    BaseSet AS (SELECT Ind = Power(2, Row_Number() OVER (ORDER BY Value) - 1), * FROM ComboSet)
    SELECT
       @Combination = N.Num,
       @Value = S.Value
    FROM
       Nums1 N
       INNER JOIN BaseSet S
          ON N.Num & S.Ind <> 0
    WHERE
       @K =
          Convert(int, Substring(@BitCounts, N.Num & 0xFF, 1))
          + Convert(int, Substring(@BitCounts, N.Num / 256 & 0xFF, 1))
          + Convert(int, Substring(@BitCounts, N.Num / 65536 & 0xFF, 1))
          + Convert(int, Substring(@BitCounts, N.Num / 16777216, 1))
    
        2
  •  6
  •   Community Stefan Steinegger    7 年前

    my original answer .

    下面是我的答案中图表的完整平均数字性能结果。

          |             Erik             |             Peter
     N  K |  CPU  Duration Reads  Writes |  CPU  Duration  Reads  Writes
    -- -- - ----- -------- ------ ------ - ----- -------- ------- ------
     1  1 |     0        0      7      0 |     0        0       7      0
     2  1 |     0        0     10      0 |     0        0       7      0
     2  2 |     0        0      7      0 |     0        0      11      0
     3  1 |     0        0     12      0 |     0        0       7      0
     3  2 |     0        0     12      0 |     0        0      13      0
     3  3 |     5        0      7      0 |     0        0      19      0
     4  1 |     0        0     14      0 |     0        0       7      0
     4  2 |     0        0     18      0 |     0        0      15      0
     4  3 |     0        0     14      0 |     5        0      27      0
     4  4 |     0        0      7      0 |     0        0      35      0
     5  1 |     5        0     16      0 |     5        0       7      0
     5  2 |     0        0     26      0 |     0        0      17      0
     5  3 |     0        0     26      0 |     0        0      37      0
     5  4 |     0        0     16      0 |     0        0      57      0
     5  5 |     0        0      7      0 |     0        0      67      0
     6  1 |     0        0     18      0 |     0        0       7      0
     6  2 |     5        0     36      0 |     0        0      19      0
     6  3 |     0        0     46      0 |     0        0      49      0
     6  4 |     0        0     36      0 |     0        0      89      0
     6  5 |     5        0     18      0 |     5        0     119      0
     6  6 |     0        0      7      0 |     0        0     131      0
     7  1 |     5        0     20      0 |     0        0       7      0
     7  2 |     0        0     48      0 |     0        0      21      0
     7  3 |     0        0     76      0 |     0        0      63      0
     7  4 |     0        0     76      0 |     0        0     133      0
     7  5 |     0        1     48      0 |     0        1     203      0
     7  6 |     5        0     20      0 |     0        1     245      0
     7  7 |     5        0      7      0 |     0        3     259      0
     8  1 |     5        2     22      0 |     0        4       7      0
     8  2 |     0        1     62      0 |     0        0      23      0
     8  3 |     0        1    118      0 |     0        0      79      0
     8  4 |     0        1    146      0 |     0        1     191      0
     8  5 |     5        3    118      0 |     0        1     331      0
     8  6 |     5        1     62      0 |     5        2     443      0
     8  7 |     0        0     22      0 |     5        3     499      0
     8  8 |     0        0      7      0 |     5        3     515      0
     9  1 |     0        2     24      0 |     0        0       7      0
     9  2 |     5        3     78      0 |     0        0      25      0
     9  3 |     5        3    174      0 |     0        1      97      0
     9  4 |     5        5    258      0 |     0        2     265      0
     9  5 |     5        7    258      0 |    10        4     517      0
     9  6 |     5        5    174      0 |     5        5     769      0
     9  7 |     0        3     78      0 |    10        4     937      0
     9  8 |     0        0     24      0 |     0        3    1009      0
     9  9 |     0        1      7      0 |     0        4    1027      0
    10  1 |    10        4     26      0 |     0        0       7      0
    10  2 |     5        5     96      0 |     0        0      27      0
    10  3 |     5        2    246      0 |     0        0     117      0
    10  4 |    10       10    426      0 |    10        4     357      0
    10  5 |    15       12    510      0 |     5        8     777      0
    10  6 |    15       16    426      0 |    10        9    1281      0
    10  7 |    10        4    246      0 |    10        9    1701      0
    10  8 |    10        5     96      0 |    10        5    1941      0
    10  9 |     5        4     26      0 |    10        7    2031      0
    10 10 |     5        0      7      0 |    10        7    2051      0
    11  1 |    10        8     28      0 |     0        0       7      0
    11  2 |    15       11    116      0 |     0        0      29      0
    11  3 |    21       24    336      0 |    10        3     139      0
    11  4 |    21       18    666      0 |     5        2     469      0
    11  5 |    21       20    930      0 |     5        3    1129      0
    11  6 |    26       35    930      0 |    15       12    2053      0
    11  7 |    20       14    666      0 |     5       25    2977      0
    11  8 |    15        9    336      0 |    20       14    3637      0
    11  9 |    10        7    116      0 |    21       27    3967      0
    11 10 |    10        8     28      0 |    36       34    4086      0
    11 11 |     5        8      7      0 |    15       15    4109      0
    12  1 |    16       18     30      0 |     5        0       7      0
    12  2 |    31       32    138      0 |     0        0      31      0
    12  3 |    31       26    446      0 |    10        2     163      0
    12  4 |    47       40    996      0 |    10        7     603      0
    12  5 |    47       46   1590      0 |    21       17    1593      0
    12  6 |    57       53   1854      0 |    31       30    3177      0
    12  7 |    41       39   1590      0 |    31       30    5025      0
    12  8 |    41       42    996      0 |    42       43    6609      0
    12  9 |    31       26    446      0 |    52       52    7607      0
    12 10 |    20       19    138      0 |    57       62    8048      0
    12 11 |    15       17     30      0 |    72       64    8181      0
    12 12 |    15       10      7      0 |    67       38    8217      0
    13  1 |    31       32     32      0 |     0        0       7      0
    13  2 |    21       25    162      0 |     0        0      33      0
    13  3 |    36       34    578      0 |     5        2     189      0
    13  4 |    57       65   1436      0 |    10        5     761      0
    13  5 |    41       40   2580      0 |    10       10    2191      0
    13  6 |    62       56   3438      0 |    31       32    4765      0
    13  7 |    62       62   3438      0 |    57       53    8251      0
    13  8 |    52       64   2580      0 |    52       47   11710      0
    13  9 |    26       28   1436      0 |    93       96   14311      0
    13 10 |    31       29    578      0 |   161      104   15891      0
    13 11 |    36       35    162      0 |   129       99   16525      0
    13 12 |    21       22     32      0 |   156       96   16383      0
    13 13 |    26       30      7      0 |   166       98   16411      0
    14  1 |    57       53     34      0 |     0        0       7      0
    14  2 |    52       50    188      0 |     0        0      35      0
    14  3 |    57       60    734      0 |    10        4     217      0
    14  4 |    78       76   2008      0 |    15        8     945      0
    14  5 |    99       97   4010      0 |    36       34    2947      0
    14  6 |   120      125   6012      0 |    41       47    6951      0
    14  7 |   125      119   6870      0 |    93       94   12957      0
    14  8 |   135      138   6012      0 |    88       98   19821      0
    14  9 |    78      153   4010      0 |   234      156   26099      0
    14 10 |    94       92   2008      0 |   229      133   30169      0
    14 11 |    83       90    734      0 |   239      136   32237      0
    14 12 |    47       46    188      0 |   281      176   33031      0
    14 13 |    52       53     34      0 |   260      167   32767      0
    14 14 |    46       47      7      0 |   203      149   32797      0
    15  1 |    83       83     36      0 |     0        0       7      0
    15  2 |   145      139    216      0 |     0        2      37      0
    15  3 |   104       98    916      0 |     0        2     247      0
    15  4 |   135      135   2736      0 |    15       17    1157      0
    15  5 |    94       97   6012      0 |    26       27    3887      0
    15  6 |   192      188  10016      0 |    57       53    9893      0
    15  7 |   187      192  12876      0 |    73       73   19903      0
    15  8 |   286      296  12876      0 |   338      230   33123      0
    15  9 |   208      207  10016      0 |   354      223   46063      0
    15 10 |   140      143   6012      0 |   443      334   56143      0
    15 11 |    88       86   2736      0 |   391      273   62219      0
    15 12 |    73       72    916      0 |   432      269   65019      0
    15 13 |   109      117    216      0 |   317      210   65999      0
    15 14 |   156      187     36      0 |   411      277   66279      0
    15 15 |   140      142      7      0 |   354      209   65567      0
    16  1 |   281      281     38      0 |     0        0       7      0
    16  2 |   141      146    246      0 |     0        0      39      0
    16  3 |   208      206   1126      0 |    10        4     279      0
    16  4 |   187      189   3646      0 |    15       13    1399      0
    16  5 |   234      234   8742      0 |    42       42    5039      0
    16  6 |   333      337  16022      0 |    83       85   13775      0
    16  7 |   672      742  22886      0 |   395      235   30087      0
    16  8 |   510      510  25746      0 |   479      305   53041      0
    16  9 |   672      675  22886      0 |   671      489   78855      0
    16 10 |   489      492  16022      0 |   859      578  101809      0
    16 11 |   250      258   8742      0 |   719      487  117899      0
    16 12 |   198      202   3646      0 |   745      483  126709      0
    16 13 |   119      119   1126      0 |   770      506  130423      0
    16 14 |   291      327    246      0 |   770      531  131617      0
    16 15 |   156      156     38      0 |   713      451  131931      0
    16 16 |   125      139      7      0 |   895      631  132037      0
    17  1 |   406      437     40      0 |     0        0       7      0
    17  2 |   307      320    278      0 |     0        0      41      0
    17  3 |   281      290   1366      0 |     0        3     313      0
    17  4 |   307      317   4766      0 |    31       28    1673      0
    17  5 |   354      378  12382      0 |    41       45    6433      0
    17  6 |   583      582  24758      0 |   130      127   18809      0
    17  7 |   839      859  38902      0 |   693      449   43873      0
    17  8 |  1177     1183  48626      0 |   916      679   82847      0
    17  9 |  1031     1054  48626      0 |  1270      944  131545      0
    17 10 |   828      832  38902      0 |  1469     1105  180243      0
    17 11 |   672      668  24758      0 |  1535     1114  219217      0
    17 12 |   422      422  12382      0 |  1494      991  244047      0
    17 13 |   474      482   4766      0 |  1615     1165  256501      0
    17 14 |   599      607   1366      0 |  1500     1042  261339      0
    17 15 |   223      218    278      0 |  1401     1065  262777      0
    17 16 |   229      228     40      0 |  1390      918  263127      0
    17 17 |   541      554      7      0 |  1562     1045  263239      0
    18  1 |   401      405     42      0 |     0        0       7      0
    18  2 |   401      397    312      0 |     0        0      43      0
    18  3 |   458      493   1638      0 |     5        6     349      0
    18  4 |   583      581   6126      0 |    16       13    1981      0
    18  5 |   697      700  17142      0 |    83      130    8101      0
    18  6 |   792      799  37134      0 |   156      162   25237      0
    18  7 |  1672     1727  63654      0 |  1098      751   62693      0
    18  8 |  1598     1601  87522      0 |  1416     1007  126423      0
    18  9 |  1849     1893  97246      0 |  2051     1522  214021      0
    18 10 |  1963     2083  87522      0 |  2734     2103  311343      0
    18 11 |  1411     1428  63654      0 |  2849     2352  398941      0
    18 12 |  1042     1048  37134      0 |  3021     2332  462671      0
    18 13 |   942      985  17142      0 |  3036     2314  499881      0
    18 14 |   656      666   6126      0 |  3052     2177  517099      0
    18 15 |   526      532   1638      0 |  2910     2021  523301      0
    18 16 |   614      621    312      0 |  3083     2108  525015      0
    18 17 |   536      551     42      0 |  2921     2031  525403      0
    18 18 |   682      680      7      0 |  3141     2098  525521      0
    19  1 |   885      909     44      0 |     0        0       7      0
    19  2 |  1411     1498    348      0 |     0        0      45      0
    19  3 |   880      887   1944      0 |     5        4     387      0
    19  4 |  1119     1139   7758      0 |    26       25    2325      0
    19  5 |  1120     1127  23262      0 |    73       72   10077      0
    19  6 |  1395     1462  54270      0 |   453      387   33591      0
    19  7 |  1875     1929 100782      0 |  1197      838   87941      0
    19  8 |  2656     2723 151170      0 |  2255     1616  188803      0
    19  9 |  3046     3092 184762      0 |  3317     2568  340053      0
    19 10 |  3635     3803 184762      0 |  5171     4041  524895      0
    19 11 |  2739     2774 151170      0 |  5577     4574  709737      0
    19 12 |  3203     3348 100782      0 |  6182     5194  860987      0
    19 13 |  1672     1750  54270      0 |  6458     5561  961849      0
    19 14 |  1760     1835  23262      0 |  6177     4964 1016199      0
    19 15 |   968     1006   7758      0 |  6266     4331 1039541      0
    19 16 |  1099     1134   1944      0 |  6208     4254 1047379      0
    19 17 |   995     1037    348      0 |  6385     4366 1049403      0
    19 18 |   916      964     44      0 |  6036     4268 1049831      0
    19 19 |  1135     1138      7      0 |  6234     4320 1049955      0
    20  1 |  1797     1821     46      0 |     0        0       7      0
    20  2 |  2000     2029    386      0 |     0        0      47      0
    20  3 |  2031     2071   2286      0 |    10        6     427      0
    20  4 |  1942     2036   9696      0 |    31       34    2707      0
    20  5 |  2104     2161  31014      0 |    88       85   12397      0
    20  6 |  2880     2958  77526      0 |   860      554   43675      0
    20  7 |  3791     3940 155046      0 |  2026     1405  121285      0
    20  8 |  5130     5307 251946      0 |  3823     2731  276415      0
    20  9 |  6547     6845 335926      0 |  5380     4148  528445      0
    20 10 |  7119     7357 369518      0 |  8271     6685  864455      0
    20 11 |  5692     5803 335926      0 |  9557     8029 1234057      0
    20 12 |  4734     4850 251946      0 | 11114     9504 1570067      0
    20 13 |  3604     3641 155046      0 | 11551    10434 1822097      0
    20 14 |  2911     2999  77526      0 | 12317    10822 1977227      0
    20 15 |  2115     2134  31014      0 | 12806    10679 2054837      0
    20 16 |  2041     2095   9696      0 | 13062     9115 2085935      0
    20 17 |  2390     2465   2286      0 | 12807     9002 2095715      0
    20 18 |  1765     1788    386      0 | 12598     8601 2098085      0
    20 19 |  2067     2143     46      0 | 12578     8626 2098555      0
    20 20 |  1640     1663      7      0 | 12932     9064 2098685      0
    21  1 |  3374     3425     48      0 |     0        0       7      0
    21  2 |  4031     4157    426      0 |     0        1      49      0
    21  3 |  3218     3250   2666      0 |    10        5     469      0
    21  4 |  3687     3734  11976      0 |    21       25    3129      0
    21  5 |  3692     3735  40704      0 |   115      114   15099      0
    21  6 |  4859     4943 108534      0 |   963      661   56079      0
    21  7 |  6114     6218 232566      0 |  2620     1880  164701      0
    21  8 |  8573     8745 406986      0 |  4999     3693  397355      0
    21  9 | 11880    12186 587866      0 |  9047     6863  804429      0
    21 10 | 13255    13582 705438      0 | 14358    11436 1392383      0
    21 11 | 13531    13807 705438      0 | 18823    15502 2097909      0
    21 12 | 12244    12400 587866      0 | 21834    18760 2803435      0
    21 13 |  9406     9528 406986      0 | 23771    21274 3391389      0
    21 14 |  7114     7180 232566      0 | 26677    24296 3798463      0
    21 15 |  4869     4961 108534      0 | 26479    23998 4031117      0
    21 16 |  4416     4521  40704      0 | 26536    22976 4139739      0
    21 17 |  4380     4443  11976      0 | 26490    19107 4180531      0
    21 18 |  3265     3334   2666      0 | 25979    17995 4192595      0
    21 19 |  3640     3768    426      0 | 26186    17891 4195349      0
    21 20 |  3234     3295     48      0 | 25688    17653 4195863      0
    21 21 |  3156     3219      7      0 | 26140    17838 4195999      0
    
        3
  •  3
  •   Peter Radocchia    14 年前

    动态SQL怎么样?

    DECLARE @k int = 5, @n INT
    
    IF OBJECT_ID('tempdb..#set') IS NOT NULL DROP TABLE #set
    CREATE TABLE #set ( [value] varchar(24) )
    INSERT #set VALUES ('1'),('2'),('3'),('4'),('5'),('6')
    SET @n = @@ROWCOUNT
    
    SELECT dbo.Factorial(@n) / (dbo.Factorial(@k) * dbo.Factorial(@n - @k)) AS [expected combinations]
    
    -- let's generate some sql.
    DECLARE 
      @crlf     NCHAR(2) = NCHAR(13)+NCHAR(10)
    , @sql      NVARCHAR(MAX)
    , @select   NVARCHAR(MAX)
    , @from     NVARCHAR(MAX)
    , @order    NVARCHAR(MAX)
    , @in       NVARCHAR(MAX)
    
    DECLARE @j INT = 0
    WHILE @j < @k BEGIN 
      SET @j += 1
      IF @j = 1 BEGIN
        SET @select = 'SELECT'+@crlf+'  _1.value AS [1]'
        SET @from   = @crlf+'FROM #set AS _1'
        SET @order  = 'ORDER BY _1.value'
        SET @in     = '[1]'
      END 
      ELSE BEGIN
        SET @select += @crlf+', _'+CONVERT(VARCHAR,@j)+'.value AS ['+CONVERT(VARCHAR,@j)+']'
        SET @from   += @crlf+'INNER JOIN #set AS _'+CONVERT(VARCHAR,@j)+' ON _'+CONVERT(VARCHAR,@j)+'.value > _'+CONVERT(VARCHAR,@j-1)+'.value'
        SET @order  += ', _'+CONVERT(VARCHAR,@j)+'.value' 
        SET @in     += ', ['+CONVERT(VARCHAR,@j)+']'
      END
    END
    SET @select += @crlf+', ROW_NUMBER() OVER ('+@order+') AS combination'
    SET @sql = @select + @from
    
    -- let's see how it looks
    PRINT @sql
    EXEC (@sql)
    
    -- ok, now dump pivot and dump into a table for later use
    IF OBJECT_ID('tempdb..#combinations') IS NOT NULL DROP TABLE #combinations
    CREATE TABLE #combinations (
      combination INT
    , value VARCHAR(24)
    , PRIMARY KEY (combination, value)
    )
    
    SET @sql 
    = 'WITH CTE AS ('+@crlf+@sql+@crlf+')'+@crlf
    + 'INSERT #combinations (combination, value)'+@crlf
    + 'SELECT combination, value FROM CTE a'+@crlf
    + 'UNPIVOT (value FOR position IN ('+@in+')) AS b'
    
    PRINT @sql
    EXEC (@sql)
    
    SELECT COUNT(DISTINCT combination) AS [returned combinations] FROM #combinations
    SELECT * FROM #combinations
    

    为@k=5生成以下查询:

    SELECT
      _1.value AS [1]
    , _2.value AS [2]
    , _3.value AS [3]
    , _4.value AS [4]
    , _5.value AS [5]
    , ROW_NUMBER() OVER (ORDER BY _1.value, _2.value, _3.value, _4.value, _5.value) AS combination
    FROM #set AS _1
    INNER JOIN #set AS _2 ON _2.value > _1.value
    INNER JOIN #set AS _3 ON _3.value > _2.value
    INNER JOIN #set AS _4 ON _4.value > _3.value
    INNER JOIN #set AS _5 ON _5.value > _4.value
    

    动态SQL很难看,您不能将其包装在UDF中,但是生成的查询非常高效。

        4
  •  2
  •   asterix    5 年前

    这个 立方体 延伸至a group by 子句表示给定列表的所有组合。例如,下面将给出一个4元素集的所有3个组合。

    select concat(a,b,c,d)
    from (select 'a','b','c','d') as t(a,b,c,d)
    group by cube(a,b,c,d)
    having len(concat(a,b,c,d)) = 3
    
        5
  •  1
  •   Justin Malinchak    8 年前

    首先创建此自定义项。。。

    CREATE FUNCTION [dbo].[_ex_fn_SplitToTable] (@str varchar(5000), @sep char(1) = null)                           
    
    RETURNS @ReturnVal table (n int, s varchar(5000))                           
    
    AS                          
    /*                          
    Alpha Test                          
    -----------                         
    select * from [dbo].[_ex_fn_SplitToTable_test01]('abcde','')                            
    */                          
    
    BEGIN                           
        declare @str2 varchar(5000)                     
        declare @sep2 char(1)                       
        if LEN(ISNULL(@sep,'')) = 0                     
        begin                       
            declare @i int                  
            set @i = 0                  
    
            set @str2 = ''                  
            declare @char varchar(1)                    
            startloop:                  
                set @i += 1             
                --print @i              
                set @char = substring(@str,@i,1)                
                set @str2 = @str2 + @char + ','             
                if LEN(@str) <= @i              
                    goto exitloop           
                goto startloop              
            exitloop:                   
            set @str2 = left(@str2,LEN(@str2) - 1)                  
            set @sep2 = ','                 
            --print @str2                   
    
        end                         
        else                        
        begin                       
            set @str2 = @str                    
            set @sep2 = @sep                    
        end                     
    
        ;WITH Pieces(n, start, stop) AS (                           
    
          SELECT 1, 1, CHARINDEX(@sep2, @str2)                          
    
          UNION ALL                             
    
          SELECT n + 1, stop + 1, CHARINDEX(@sep2, @str2, stop + 1)                             
    
          FROM Pieces                           
    
          WHERE stop > 0                            
    
        )                           
    
        insert into @ReturnVal(n,s)                             
        SELECT n,                           
          SUBSTRING(@str2, start, CASE WHEN stop > 0 THEN stop-start ELSE 5000 END) AS s                            
        FROM Pieces option (maxrecursion 32767)                             
    
    
        RETURN                          
    
    END                         
    
    
    
    GO                          
    

    然后创建这个存储过程。。。

    CREATE proc [CombinationsOfString]                          
    (                           
        @mystring varchar(max) = '0,5,10,15,20,25'                      
    )                           
    as                          
    
    /*                          
    ALPHA TEST                          
    ---------                           
    exec CombinationsOfString '-20,-10,0,10,20'                         
    */                          
    if object_id('tempdb..#_201606070947_myorig') is not null drop table #_201606070947_myorig                          
    
    CREATE TABLE #_201606070947_myorig                          
     (                          
       SourceId  int      not null  identity(1,1)                           
      ,Element   varchar(100)  not null                         
     )                          
    insert into #_201606070947_myorig                           
    select s from dbo._ex_fn_SplitToTable(@mystring,',')                            
    
    --select SourceId, Element from #_201606070947_myorig                           
    
    declare @mynumerics varchar(max)                            
    
    set @mynumerics = (                         
        select STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(SourceId)) AS 'data()'                      
        FROM #_201606070947_myorig                      
        FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands                      
    )                           
    
    set @mynumerics = REPLACE(@mynumerics,' ','')                           
    
    print @mynumerics                           
    
    if object_id('tempdb..#_201606070947_source') is not null drop table #_201606070947_source                          
    if object_id('tempdb..#_201606070947_numbers') is not null drop table #_201606070947_numbers                            
    if object_id('tempdb..#_201606070947_results') is not null drop table #_201606070947_results                            
    if object_id('tempdb..#_201606070947_processed') is not null drop table #_201606070947_processed                            
    
    
    
    CREATE TABLE #_201606070947_source                          
     (                          
       SourceId  int      not null  identity(1,1)                           
      ,Element   char(1)  not null                          
     )                          
    
    --declare @mynumerics varchar(max)                          
    --set @mynumerics = '1,2,3,4,5'                         
    insert into #_201606070947_source                           
    select s from dbo._ex_fn_SplitToTable(@mynumerics,',')                          
    
    -- select * from #_201606070947_source                          
    
    declare @Length int                         
    set @Length = (select max(SourceId) from #_201606070947_source)                         
    declare @columnstring varchar(max) = (SELECT REPLICATE('c.',@Length))                           
    print @columnstring                         
    declare @subs varchar(max) = (SELECT REPLICATE('substring.',@Length))                           
    print @subs                         
    
    if object_id('tempdb..#_201606070947_columns') is not null drop table #_201606070947_columns                            
    
    select s+CONVERT(varchar,dbo.PadLeft(convert(varchar,n),'0',3)) cols                            
    into #_201606070947_columns                         
    from [dbo].[_ex_fn_SplitToTable](@columnstring,'.') where LEN(s) > 0                            
    
    if object_id('tempdb..#_201606070947_subcolumns') is not null drop table #_201606070947_subcolumns                          
    select s+'(Combo,'+CONVERT(varchar,n)+',1) ' + 'c'+CONVERT(varchar,dbo.PadLeft(convert(varchar,n),'0',3)) cols                          
    into #_201606070947_subcolumns                          
    from [dbo].[_ex_fn_SplitToTable](@subs,'.') where LEN(s) > 0                            
    
    -- select * from #_201606070947_subcolumns                          
    -- select * from #_201606070947_columns                         
    
    
    declare @columns_sql varchar(max)                           
    set @columns_sql =                          
            (                   
                select distinct                 
                  stuff((SELECT distinct + cast(cols as varchar(50)) +  ' VARCHAR(1), '             
                           FROM (       
                            select cols 
                            from #_201606070947_columns     
                            ) t2    
                           --where t2.n = t1.n      
                           FOR XML PATH('')),3,0,'')        
                from (              
                    select cols         
                    from #_201606070947_columns             
                    ) t1            
            )                   
    
    declare @substring_sql varchar(max)                         
    set @substring_sql =                            
            (                   
                select distinct                 
                  stuff((SELECT distinct + cast(cols as varchar(100)) +  ', '               
                           FROM (       
                            select cols 
                            from #_201606070947_subcolumns  
                            ) t2    
                           --where t2.n = t1.n      
                           FOR XML PATH('')),3,0,'')        
                from (              
                    select cols         
                    from #_201606070947_subcolumns          
                    ) t1            
            )                   
    set @substring_sql = left(@substring_sql,LEN(@substring_sql) - 1)                           
    print @substring_sql                            
    
    set @columns_sql = LEFT(@columns_sql,LEN(@columns_sql) - 1)                         
    --SELECT @columns_sql                           
    declare @sql varchar(max)                           
    set @sql = 'if object_id(''tempdb..##_201606070947_01'') is not null drop table ##_201606070947_01 create table ##_201606070947_01 (rowid int,' + @columns_sql + ')'                            
    print @sql                          
    execute(@sql)                           
    
    CREATE TABLE #_201606070947_numbers (Number int not null)                           
    
    insert into #_201606070947_numbers                          
    select SourceId from #_201606070947_source                          
    
    CREATE TABLE #_201606070947_results                         
     (                          
       Combo   varchar(10)  not null                            
      ,Length  int          not null                            
     )                          
    
     SET NOCOUNT on                         
    
    DECLARE                         
      @Loop     int                         
     ,@MaxLoop  int                         
    
    
    --  How many elements there are to process                          
    SELECT @MaxLoop = max(SourceId)                         
     from #_201606070947_source                         
    
    
    --  Initialize first value                          
    TRUNCATE TABLE #_201606070947_results                           
    INSERT #_201606070947_results (Combo, Length)                           
     select Element, 1                          
      from #_201606070947_source                            
      where SourceId = 1                            
    
    SET @Loop = 2                           
    
    --  Iterate to add each Element after the first                         
    WHILE @Loop <= @MaxLoop                         
     BEGIN                          
    
        INSERT #_201606070947_results (Combo, Length)                       
         select distinct                        
            left(re.Combo, @Loop - nm.Number)                   
            + so.Element                    
            + RIGHT(re.Combo, nm.Number - 1)                    
           ,@Loop                       
          from #_201606070947_results re                        
           inner join #_201606070947_numbers nm                     
            on nm.Number <= @Loop                   
           inner join #_201606070947_source so                      
            on so.SourceId = @Loop                  
          where re.Length = @Loop - 1                       
    
        SET @Loop = @Loop + 1                       
     END                            
    -- select * from #_201606070947_results                         
    --  Show #_201606070947_results                         
    SELECT *                            
    into #_201606070947_processed                           
     from #_201606070947_results                            
     where Length = @MaxLoop                            
     order by Combo                         
    
    
    
    -- select * from #_201606070947_processed                           
    set @sql = 'if object_id(''tempdb..##_201606070947_02'') is not null drop table ##_201606070947_02 '                            
    print @sql                          
    execute(@sql)                           
    
    set @sql = ' ' +                            
    '  SELECT ROW_NUMBER() OVER(ORDER BY Combo Asc) AS RowID,' + @substring_sql +                           
    '  into ##_201606070947_02 ' +                          
    ' FROM #_201606070947_processed ' +                         
    ' '                         
    PRINT @sql                          
    execute(@sql)                           
    
    declare @columns_sql_new varchar(max)                           
    set @columns_sql_new = REPLACE(@columns_sql,'(1)','(100)')                          
    set @sql = 'if object_id(''tempdb..##_201606070947_03'') is not null drop table ##_201606070947_03 create table ##_201606070947_03 (RowId int,' + @columns_sql_new + ')'                            
    PRINT @sql                          
    execute(@sql)                           
    
    insert into ##_201606070947_03 (RowId)                          
    select RowId from ##_201606070947_02                            
    
    --select * from ##_201606070947_03                          
    
    
    
    
    DECLARE @ColumnId varchar(10)                           
    DECLARE @getColumnId CURSOR                         
    SET @getColumnId = CURSOR FOR                           
        select cols ColumnId from #_201606070947_columns                        
    OPEN @getColumnId                           
    FETCH NEXT                          
    FROM @getColumnId INTO @ColumnId                            
    WHILE @@FETCH_STATUS = 0                            
    BEGIN                           
        PRINT @ColumnId                     
        set @sql = ' ' +                        
        ' update ##_201606070947_03                         
          set ' + @ColumnId + ' = B.Element                         
          from ##_201606070947_03 A                         
          , (                       
    
                select A.RowID, B.*             
                from                
                (               
                    select * from ##_201606070947_02            
                ) A             
                ,               
                (               
                    select * from #_201606070947_myorig         
                ) B             
                where A.' + @ColumnId + ' = B.SourceId              
            ) B                 
           where A.RowId = B.RowId                      
        '                       
        execute(@sql)                       
        print @sql                      
    
    FETCH NEXT                          
    FROM @getColumnId INTO @ColumnId                            
    END                         
     CLOSE @getColumnId                         
    DEALLOCATE @getColumnId                         
    
    select * from ##_201606070947_03                            
    
        6
  •  0
  •   ErikE Russ Cam    12 年前

    不管怎样,我确信我已经把自己拉下水了——在这里我想我会找到一些非常酷的东西,但后来发现我的解决方案很容易被超越。但令我惊讶的是,当我尝试这项技术时,它比上面最好的方法更糟糕。由于它易于实现,并且对于小型作业具有合理的性能(因此使其具有优越性,除非作业可能需要诸如位模式技术之类的额外复杂性),因此在这里提供参考。

    给定一个表#集合,其行数与所选项目的行数相同,且变量@K表示一次所取项目的数量,则此方法如下:

    WITH Chains AS (
       SELECT
          Generation = 1,
          Chain = Convert(varchar(1000),'|' + Value + '|'),
          Value
       FROM
          #Set
       WHERE
          @K > 0
          AND value <= ALL (
             SELECT TOP (@K) Value
             FROM #Set
             ORDER BY Value DESC
          )
       UNION ALL
       SELECT
          C.Generation + 1,
          Convert(varchar(1000), C.Chain + S.Value + '|'),
          S.Value
       FROM
          Chains C
          INNER JOIN #Set S
             ON C.Value < S.Value
       WHERE
          C.Generation <= @K
    )
    SELECT
       C.Chain,
       S.Value
    FROM
       Chains C
       INNER JOIN #Set S
          ON C.Chain LIKE '%|' + S.Value + '|%'
    WHERE
       Generation = @K;