生成SQL中的所有组合

前端之家收集整理的这篇文章主要介绍了生成SQL中的所有组合前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我需要在给定的一组@ @中生成size @k的所有组合.有人可以查看以下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];

解决方法

回归组合

使用数字表或数字生成CTE,选择0到2 ^ n – 1.使用这些数字中包含1的位位置表示组合中存在或不存在相对成员,并消除那些没有正确的数值,你应该能够返回一个结果集与所有你想要的组合.

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开始,首先获得一次所需的正确数量.这样可以快3到3.5倍(cpu和时间):

WITH Nums AS (
   SELECT Num,P1 = (Num & 0x55555555) + ((Num / 2) & 0x55555555)
   FROM dbo.Numbers
   WHERE Num BETWEEN 0 AND POWER(2,Nums2 AS (
   SELECT Num,P2 = (P1 & 0x33333333) + ((P1 / 4) & 0x33333333)
   FROM Nums
),Nums3 AS (
   SELECT Num,P3 = (P2 & 0x0f0f0f0f) + ((P2 / 16) & 0x0f0f0f0f)
   FROM Nums2
),*
   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,但是通过比特算术,这可能会更好.当我有机会尝试一下,看看它堆叠起来.

我的性能声明是基于没有ORDER BY子句运行的查询.为了清楚起见,这个代码正在从Numbers表中计数Num中的1位的数目.这是因为该数字被用作一种索引器来选择当前组合中的哪些元素,因此1位的数量将是相同的.

我希望你喜欢它!

为了记录,使用整数的位模式来选择一组的成员的技术是我创造了“垂直交叉连接”.它有效地导致多组数据的交叉连接,其中集合&交叉连接是任意的.这里,套数是一次拍摄的物品数量.

实际上交叉加入通常的横向意义(每个连接添加更多的列到现有的列列表)将看起来像这样:

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'

我上面的查询有效地“交叉连接”多次,只需要一个连接.与实际的交叉连接相比,结果是不受欢迎的,但这是一个小问题.

批评你的代码

首先,我可以建议你对这个UDF的这个修改

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

现在你可以计算出更大的组合,加上更有效率.您甚至可以考虑使用十进制(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,模式是A__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);

彼得表示,这个“垂直交叉连接”并不像简单地编写动态sql那样做,实际上它可以避免CROSS JOIN.他的解决方案以轻微的成本读取更多的10到17倍.随着工作量的增加,他的查询性能下降速度比我的速度快,但是速度不足以阻止任何人使用它.

下面的第二组数字是除以表中第一行的因子,只是为了显示它如何缩放.

埃里克

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

Extrapolating,最终我的查询将会更便宜(虽然它是从开始读),但不是很长时间.要使用集合中的21个项目已经需要一个数字表,最多可以达到2097152 …

这是我最初发表的一个意见,意识到我的解决方案会在一个动态的数字表格上表现得更好:

I love single-query solutions to problems like this,but if you’re looking for the best performance,an actual cross-join is best,unless you start dealing with serIoUsly huge numbers of combination. But what does anyone want with hundreds of thousands or even millions of rows? Even the growing number of reads don’t seem too much of a problem,though 6 million is a lot and it’s getting bigger fast…

Anyway. Dynamic sql wins. I still had a beautiful query. 原文链接:https://www.f2er.com/mssql/81770.html

猜你在找的MsSQL相关文章