[英]Generate all combinations in SQL
我需要在給定的一組大小@n
生成大小@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中提取以首先獲得一次獲取正確數量的項目。 這比 CPU 和時間快 3 到 3.5 倍:
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 但一直使用位算術,這會表現得更好。 當我有機會時,我會嘗試一下,看看效果如何。
我的性能聲明基於沒有 ORDER BY 子句運行的查詢。 為清楚起見,此代碼所做的是計算Numbers
表中Num
中設置的 1 位的Numbers
。 這是因為數字被用作一種索引器來選擇集合的哪些元素在當前組合中,因此 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
現在,您可以計算更大的組合集,而且效率更高。 您甚至可以考慮使用decimal(38, 0)
來允許在您的組合計算中進行更大的中間計算。
其次,您給定的查詢沒有返回正確的結果。 例如,使用我在下面的性能測試中的測試數據,集合 1 與集合 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);
Peter 表明,這種“垂直交叉聯接”的性能不如簡單地編寫動態 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
推斷,最終我的查詢會更便宜(盡管它是從讀取開始的),但不會持續很長時間。 要使用集合中的 21 個項目已經需要一個數字表,直到 2097152...
這是我最初發表的評論,然后才意識到我的解決方案在使用動態數字表時會表現得更好:
我喜歡針對此類問題的單查詢解決方案,但是如果您正在尋找最佳性能,那么實際的交叉連接是最好的,除非您開始處理大量的組合。 但是,對於數十萬甚至數百萬行,人們想要什么? 即使讀取次數不斷增加似乎也不是什么大問題,盡管 600 萬已經很多了,而且還在快速增長……
反正。 動態 SQL 獲勝。 我仍然有一個漂亮的查詢。 :)
當我最初寫這個答案時,我說:
請注意,您可以使用動態數字表,但我還沒有嘗試過。
嗯,我試了一下,結果是效果好多了! 這是我使用的查詢:
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;
請注意,我將這些值選擇到變量中以減少測試所有內容所需的時間和內存。 服務器仍然執行所有相同的工作。 我將 Peter 的版本修改為類似的,並刪除了不必要的額外內容,使它們盡可能精簡。 用於這些測試的服務器版本是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)
在 VM 上運行。
下面的圖表顯示了 N 和 K 值高達 21 的性能曲線。它們的基礎數據在本頁的另一個答案中。 這些值是每個查詢在每個 K 和 N 值上運行 5 次的結果,然后拋出每個指標的最佳和最差值,並對剩余的 3 個進行平均。
基本上,我的版本在 N 的高值和 K 的低值處有一個“肩部”(在圖表的最左角),這使得它在那里的表現比動態 SQL 版本差。 然而,這保持相當低和恆定,並且 N = 21 和 K = 11 附近的中心峰值對於 Duration、CPU 和 Reads 比動態 SQL 版本低得多。
我包含了每個項目預計返回的行數的圖表,以便您可以查看查詢的執行情況與它必須完成的工作的大小。
有關完整的性能結果,請參閱我在此頁面上的其他答案。 我達到了帖子字符限制,無法將其包含在此處。 (有什么想法可以放在哪里?)為了將事情與我的第一個版本的性能結果進行比較,這里的格式與以前相同:
埃里克
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
持續時間分析
閱讀分析
持續時間分析顯示我的查詢以顯着但不是過大的數量獲勝。 當指標切換到讀取時,會出現一個非常不同的畫面——我的查詢平均使用 1/10 的讀取。
我很想看到更新的動態 SQL 版本的結果,該版本對如上所述的每個步驟中選擇的項目設置了額外的上限。
我的查詢的以下版本比上面列出的性能結果提高了大約 2.25%。 我使用了 MIT 的 HAKMEM 位計數方法,並在row_number()
的結果上添加了一個Convert(int)
,因為它返回一個 bigint。 當然,我希望這是我用於上述所有性能測試、圖表和數據的版本,但我不太可能重做,因為它是勞動密集型的。
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))
請原諒這個額外的答案。 我在原始答案中遇到了帖子字符限制。
以下是我的答案中圖表的完整平均數字性能結果。
| 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
一些動態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 中,但生成的查詢非常高效。
group by
子句的CUBE擴展表示給定列表的所有組合。 例如,以下將給出 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
首先創建這個UDF...
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
我遇到了另一種計算組合的技術,它非常簡單。 在公開的 SQL 挑戰中,基於與我(當前)接受的答案相同的位模式技術,它也完全擊敗了我自己的嘗試。 雖然我承認我沒有玩過很長時間,或者從這里獲取我最好的解決方案並進行調整,但我又重新寫了一遍。
無論如何,我確信我已經把自己放在了一個釘子上——在這里我以為我找到了一些很酷的東西,但后來發現我的解決方案很容易被超越。 但令我驚訝的是,當我嘗試該技術時,它比上面最好的方法還要糟糕。 由於它易於實施且對小型作業具有合理的性能,因此在此處供參考(因此使其更出色,除非作業可能需要增加位模式技術之類的復雜性)。
給定一個表#Set,其行數與從中選擇的項目相同,變量@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;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.