问题描述
我想对多个列之间相互关联的标识符进行分组,并创建/分配一个唯一的组 ID。
另外,如果我们收到一个新行,我们可以根据之前为其他组 id 所做的事情分配正确的 id
例如:
Col1 | Col2 | Col3 | Col4 |
---|---|---|---|
AA | 空 | 33 | 12 |
BB | 空 | 45 | 12 |
AA | 123 | 65 | 15 |
抄送 | 123 | NULL | 42 |
DD | 空 | 10 | 42 |
EE | NULL | 20 | NULL |
FF | 145 | 33 | NULL |
GG | NULL | NULL | 11 |
想要的结果:
组 ID =1 因为在 col1,它是相同的值第 1 行和第 3 (AA) 行,对于第 4 行它也是 ID 1,因为在第二列中,AA 的值是 123(CC 相同) 如果行和列之间有任何匹配,我们生成一个 id
Col1 | 第 2 列 | 第 3 列 | 第 4 列 | 组 ID |
---|---|---|---|---|
AA | 空 | 33 | 12 | 1 |
BB | 空 | 45 | 12 | 1 |
AA | 123 | 65 | 15 | 1 |
抄送 | 123 | NULL | 42 | 1 |
DD | 空 | 10 | 42 | 1 |
EE | NULL | 20 | NULL | 2 |
FF | 145 | 33 | NULL | 1 |
GG | NULL | NULL | 11 | 3 |
解决方法
我一直在做这方面的一些工作并同意 Kashyap-我找不到一种方法来做到这一点是一个单一的声明。您需要递归 CTE 或循环。 Synapse 目前不支持递归 CTE,因此需要使用循环来创建您想要的效果。
在我处理这个问题时出现的一个问题。随着您继续添加数据,您将有越来越多的重叠,最终可能只有一组。这取决于你的数据集——你可能有一些你可以保证会有离散部门的东西。根据我编写的脚本的工作方式,新匹配将更新任何组 ID,即使在现有数据中也是如此。您可以将其修改为仅为新行设置组 ID,但最终可能会出现一行匹配多个组的情况。
当然不是唯一的选择,但这是我整理的脚本。它依赖于具有在每次迭代中保持不变的唯一 ID。因为循环使用更新而不是插入,准备数据将涉及将数据插入到没有组的新表中,并且您可以在那时使用自动增量或其他方式创建您的 ID。该脚本最适用于 INT ID 列,但在必要时应与 guid 一起使用。
所以过程基本上是这样的:
- 做任何你需要做的初始准备工作,将数据插入到表中并创建一个 ID
- 将表重新连接到自身,对于可能包含匹配项的每一列进行一次
- 将组 ID 更新为该组匹配项的 ID 和当前组 ID 中的最小值。
- 检查我们是否需要再做一轮。因为我们使用最小 ID 作为组号,所以每组中会有一行
ID = group ID
CREATE TABLE #testtable
(
[id] INT NOT NULL,[col1] INT NOT NULL,[col2] INT NULL,[col3] INT NULL,[groupnumber] INT NULL
)
INSERT INTO #testtable
(id,col1,col2,col3)
INSERT INTO #testTable (id,col3)
SELECT 1,1,5,33 UNION ALL -- First
SELECT 2,2,null,45 UNION ALL -- Second
SELECT 3,123,65 UNION ALL -- First
SELECT 4,3,null UNION ALL -- First
SELECT 5,10,10 UNION ALL -- Third
SELECT 6,45 UNION ALL -- Second
SELECT 7,6,145,33 -- First
DECLARE @RemainingRows INT,@LoopCounter INT,@MaxLoops int -- To protect against infinite loop
SET @RemainingRows = (SELECT COUNT([id]) FROM #testtable)
SET @LoopCounter = 0;
SET @MaxLoops = 10;
WHILE( @RemainingRows > 0
AND @LoopCounter < @MaxLoops )
BEGIN
WITH combineddata AS
(
SELECT
id,col3,groupnumber
FROM
#testtable
),--Create a set a rows that contains all rows and all possible matches
matcheddata AS
(
SELECT
c1.id,c1.col1 AS c1col1,c1.col2 AS c1col2,c1.col3 AS c1col3,c1.groupnumber AS groupNumber1,c2.id AS RowNum2,c2.groupnumber AS groupNumber2,c3.id AS RowNum3,c3.groupnumber AS groupNumber3,c4.id AS RowNum4,c4.groupnumber AS groupNumber4
FROM
combineddata c1
LEFT JOIN
combineddata c2
ON c1.col1 = c2.col1
LEFT JOIN
combineddata c3
ON c1.col2 = c3.col2
LEFT JOIN
combineddata c4
ON c1.col3 = c4.col3
)
UPDATE #testtable
SET
groupnumber =
CASE
WHEN
NEW.groupnumber IS NULL
THEN
NULL
ELSE
NEW.groupnumber
END
FROM
(
SELECT
id,c1col1,c1col2,c1col3,MIN(groupnumber) AS GroupNumber
FROM
matcheddata CROSS apply (
SELECT
MIN(c) AS GroupNumber
FROM (VALUES
(id),(RowNum2),(RowNum3),(RowNum4),(groupNumber1),(groupNumber2),(groupNumber3),(groupNumber4)
) AS v (C)
WHERE
c IS NOT NULL) g
GROUP BY
id,c1col3
) NEW
INNER JOIN # testtable
ON NEW.id = #testtable.id
SET
@LoopCounter = @LoopCounter + 1
SET
@RemainingRows =
(
SELECT
COUNT(t1.id)
FROM
#testtable t1
LEFT JOIN
#testtable t2
ON t1.groupnumber = t2.[id]
WHERE
t2.id IS NULL
OR t2.id <> t2.groupnumber
)
PRINT 'Remaining Rows: ' + CAST(@RemainingRows AS VARCHAR) PRINT 'Counter: ' + CAST(@LoopCounter AS VARCHAR);
END
SELECT * FROM #testtable
IF Object_id('tempdb..#testTable') IS NOT NULL
BEGIN
DROP TABLE # testtable
END```