多列之间相互关联的组标识符/值

问题描述

我想对多个列之间相互关联的标识符进行分组,并创建/分配一个唯一的组 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 一起使用。

所以过程基本上是这样的:

  1. 做任何你需要做的初始准备工作,将数据插入到表中并创建一个 ID
  2. 将表重新连接到自身,对于可能包含匹配项的每一列进行一次
  3. 将组 ID 更新为该组匹配项的 ID 和当前组 ID 中的最小值。
  4. 检查我们是否需要再做一轮。因为我们使用最小 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```