我有一个很大的表(60m行),其中包含两列的行:set_id和object_id。 set_id用于标识object_id的组。就我而言,这些object_id可以出现在多个集合中。
示例:
set_id | object_id
1 | 100
1 | 101
1 | 102
2 | 100
2 | 201
3 | 300
4 | 102
4 | 300
5 | 500
我需要检索一组至少共享一个object_id的set_id对的列表。每个set_id也将与其自身配对。配对仅显示一次(即:(1,2),但不显示(2,1))。对于上面的示例:
set_id_A | set_id_B
1 | 1
1 | 2
1 | 4
2 | 2
3 | 3
3 | 4
4 | 4
5 | 5
编写查询以实现此目标非常简单。问题是我的解决方案无法很好地扩展。这是我的代码:
-- #original_sets table created
CREATE TABLE #original_sets
(
[set_id] INT,
[object_id] BIGINT
);
-- #original_sets populated here from other data
-- removed
-- index created on table:
CREATE CLUSTERED INDEX cx_original_sets
ON #original_sets ([object_id], [set_id]);
-- code to create the pairs:
SELECT
ck1.[set_id] AS set_id_A,
ck2.[set_id] AS set_id_B
FROM
#original_sets ck1
INNER JOIN
#original_sets ck2
ON ck1.[object_id] = ck2.[object_id]
AND ck1.[set_id] <= ck2.[set_id]
GROUP BY
ck1.[set_id],
ck2.[set_id];
如果original_sets表很小甚至是中等大小,那将是非常快的,但是一旦我到达6000万行,它的速度真的很慢。我最终在10小时后取消了它,所以我不确定它是否会结束。
在如此大的桌子上进行自我联接只是在寻找麻烦,我知道。还有另一种方法可以更好地扩展吗?谢谢!
修改1: 可能有助于提高性能的另一件事:获得集对之后,我还有另一个过程,该过程然后创建包含与原始集相关的所有对象ID的超级集(请参阅:传递闭包聚类http://sqlblog.com/blogs/davide_mauri/archive/2017/11/12/lateral-thinking-transitive-closure-clustering-with-sql-server-uda-and-json.aspx顶部显示得很好)
因为在此之后我要这样做,所以我并不真正在乎set_ids本身,而只是在乎如何将object_ids组合在一起。因此可以安全地消除重复集。也许首先这样做会是减小表的整体大小的好方法。
编辑2:
尝试减小原始表大小的新版本
-- #original_sets table created
CREATE TABLE #original_sets
(
[set_id] INT,
[object_id] BIGINT
);
-- #original_sets populated here from other data
-- removed
-- index created on table:
CREATE CLUSTERED INDEX cx_original_sets
ON #original_sets ([object_id], [set_id]);
--added this index:
CREATE CLUSTERED INDEX IDX_original_sets
ON #original_sets ([set_id], [object_id]);
-- added this part to identify sets with only one object_id
CREATE TABLE #lonely_sets
(
[set_id] INT PRIMARY KEY
);
INSERT INTO #lonely_sets
SELECT
[set_id]
FROM
#original_sets
GROUP BY
[set_id]
HAVING
COUNT(*) = 1
--then use that data to eliminate duplicate single object sets (see edit 1 for why)
DELETE FROM #original_sets
WHERE set_id IN
(
SELECT
[set_id ]
FROM
#lonely_sets lonely_sets
WHERE
[set_id ] NOT IN
(
SELECT
MIN(original_sets.[set_id ])
FROM
#original_sets original_sets
INNER join #lonely_sets lonely_sets
ON original_sets.set_id = lonely_sets.set_id
GROUP BY
original_sets.[object_id]
)
)
-- then run this
-- code to create the pairs as before:
SELECT
ck1.[set_id] AS set_id_A,
ck2.[set_id] AS set_id_B
FROM
#original_sets ck1
INNER JOIN
#original_sets ck2
ON ck1.[object_id] = ck2.[object_id]
AND ck1.[set_id] <= ck2.[set_id]
GROUP BY
ck1.[set_id],
ck2.[set_id];
额外的工作将原始集减少到约1600万行。 使用〜1m个唯一的object_ids和〜7m个唯一的set_ids。
以下是每组对象的细分:
object_count_per_set | sets_with_that_count
67 32
49 8
42 197
41 120
38 1
37 101
35 16
30 23
29 18
28 109
27 1643
26 382
25 43
24 35
23 8
22 492
21 703
20 339
19 1548
18 2176
17 358
16 1156
15 852
14 1755
13 1845
12 2452
11 3073
10 4570
9 4723
8 9726
7 16178
6 35493
5 81091
4 211305
3 724627
2 5360781
1 789573
因此,总体来说,要处理的表要小得多,但是完成了一个多小时(影响了1,035,212,815行),运行起来仍然很慢。
我知道有很多重复集可以安全地消除,我只需要一个好的方法即可。
答案 0 :(得分:0)
您说表中有6000万行,大约50m个唯一的set_id和100k个唯一的object_id。
因此,平均每个object_id具有600行。平均而言,ck1.[object_id] = ck2.[object_id] AND ck1.[set_id] <= ck2.[set_id]
会为每条外部行匹配300行,因此当前您的查询正在生成和聚合大约180亿行的内容
5000万套ID和6000万行意味着大多数套只能与自己配对,
首先,我会想到只使用简单的GROUP BY ... COUNT
找到这些保证的非配对集,然后通过三角自连接将它们排除在较昂贵的部分中。
如果该查询仍然太慢,请提供有关#paired_sets
的特征的信息,包括行数和不同的object_id
和set_id
的数量以及最大的大小object_id
在那里(行数)
CREATE TABLE #lonely_sets
(
[set_id] INT PRIMARY KEY
);
INSERT INTO #lonely_sets
SELECT [set_id]
FROM #original_sets
GROUP BY [set_id]
HAVING COUNT(*) = 1;
CREATE TABLE #paired_sets
(
[set_id] INT,
[object_id] INT,
PRIMARY KEY ([object_id], [set_id])
);
INSERT INTO #paired_sets
SELECT [set_id], [object_id]
FROM #original_sets
WHERE [set_id] NOT IN (SELECT ls.set_id FROM #lonely_sets ls);
--Final Select
SELECT [set_id] AS set_id_A, [set_id] AS set_id_B
FROM #lonely_sets
UNION ALL
SELECT
ck1.[set_id] AS set_id_A,
ck2.[set_id] AS set_id_B
FROM
#paired_sets ck1
INNER JOIN
#paired_sets ck2
ON ck1.[object_id] = ck2.[object_id]
AND ck1.[set_id] <= ck2.[set_id]
GROUP BY
ck1.[set_id],
ck2.[set_id];
答案 1 :(得分:0)
因此,根据Martin的建议,我被指示减少连接表的大小,这就是我要结束的地方:
我决定尝试消除重复的集合(请参见上面我的原始帖子中的Edit 1)。 就我而言,这应该做两件事:减少以后运行自连接的表的大小,并帮助逐步扩展(每周都会引入新集合,但它们通常是以前集合的重复)。
我使用了旧的XML PATH行串联技巧(我没有运行2017,否则STRING_AGG可能会更快?)创建一个用分号分隔的列表,其中列出了每个set_id中的所有object_id。
然后用于标识包含相同object_id组的set_id,因此可以安全地将其删除。这样行数从60m减少到1m。就我而言,这大约需要50分钟。有没有更快的方法来识别相同的集合?我不确定。
然后创建一个过滤的集合表,并基于自连接来创建关系表。有了新的过滤后的数据,运行查询的那部分时间现在只有几分钟。
该过程中最慢的部分是不到一个小时的XML PATH行concat查询。这并不理想,但是由于此过程是紧急维护程序的一部分,因此我愿意接受运行所花费的时间。
代码:
-- #original_sets table created
CREATE TABLE #original_sets
(
[set_id] INT,
[object_id] BIGINT
);
-- #original_sets populated here from other data
-- removed
-- index created on table:
CREATE CLUSTERED INDEX cx_original_sets
ON #original_sets ([object_id], [set_id]);
CREATE CLUSTERED INDEX IDX_original_sets
ON #original_sets ([set_id], [object_id]);
----------------------------------------------------------
CREATE TABLE #filtered_sets
(
[set_id] INT,
[object_id] BIGINT
);
INSERT INTO #filtered_sets
SELECT
original_sets.set_id,
original_sets.[object_id]
FROM
#original_sets original_sets
INNER JOIN
(
SELECT
MIN(set_id) AS set_id
FROM
(
SELECT DISTINCT
set_id,
STUFF(
(
SELECT
'; ' + CAST(original_sets.object_id AS VARCHAR(20))
FROM
#original_sets original_sets
WHERE
original_sets.set_id = s2.set_id
ORDER BY
original_sets.object_id
FOR XML PATH('')
), 1, 2, ''
) AS object_id_list
FROM
#original_sets s2
GROUP BY
set_id
) a
GROUP BY
object_id_list
) unique_sets
ON original_clusters.cluster_id = unique_sets.cluster_id
CREATE CLUSTERED INDEX cx_filtered_sets
ON #filtered_sets ([object_id], [set_id]);
CREATE NONCLUSTERED INDEX IDX_filtered_sets
ON #filtered_sets ([set_id],[object_id]);
----------------------------------------------------------
-- then run this
-- code to create the pairs as before:
SELECT
ck1.[set_id] AS set_id_A,
ck2.[set_id] AS set_id_B
FROM
#filtered_sets ck1
INNER JOIN
#filtered_sets ck2
ON ck1.[object_id] = ck2.[object_id]
AND ck1.[set_id] <= ck2.[set_id]
GROUP BY
ck1.[set_id],
ck2.[set_id];
我接受了马丁的回答,因为它对于指出我需要去的地方很有用。谢谢!