Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Linker Join mit dem nächsten Wert ohne Duplikate

Nachfolgend finden Sie eine satzbasierte Lösung mit CTEs und Windowing-Funktionen.

Die ranked_matches CTE weist jeder Zeile in TableA einen Rang der nächsten Übereinstimmung zu zusammen mit einem Rang der nächsten Übereinstimmung für jede Zeile in TableB , unter Verwendung des index Wert als Tie-Breaker.

Die best_matches CTE gibt Zeilen von ranked_matches zurück die für beide Rankings den besten Rang (Rangwert 1) haben.

Schließlich verwendet die äußere Abfrage einen LEFT JOIN aus TableA zu den zu den best_matches CTE, um die TableA einzuschließen Zeilen, denen keine beste Übereinstimmung zugewiesen wurde, da die engste Übereinstimmung bereits zugewiesen wurde.

Beachten Sie, dass dies keine Übereinstimmung für die in Ihren Beispielergebnissen angegebene TabelleA-Zeile von Index 3 zurückgibt. Die engste Übereinstimmung für diese Zeile ist TableB-Index 3, eine Differenz von 83. Diese TableB-Zeile ist jedoch eine engere Übereinstimmung mit der TableA-Index-2-Zeile, eine Differenz von 14, sodass sie bereits zugewiesen wurde. Bitte klären Sie Ihre Frage, wenn dies nicht das ist, was Sie wollen. Ich denke, diese Technik kann entsprechend angepasst werden.

CREATE TABLE dbo.TableA(
      [index] int NOT NULL
        CONSTRAINT PK_TableA PRIMARY KEY
    , value int
    );
CREATE TABLE dbo.TableB(
      [index] int NOT NULL
        CONSTRAINT PK_TableB PRIMARY KEY
    , value int
    );
INSERT  INTO dbo.TableA
        ( [index], value )
VALUES  ( 1, 123 ),
        ( 2, 245 ),
        ( 3, 342 ),
        ( 4, 456 ),
        ( 5, 608 );

INSERT  INTO dbo.TableB
        ( [index], value )
VALUES  ( 1, 152 ),
        ( 2, 159 ),
        ( 3, 259 );

WITH 
      ranked_matches AS (
        SELECT 
              a.[index] AS a_index
            , a.value AS a_value
            , b.[index] b_index
            , b.value AS b_value
            , RANK() OVER(PARTITION BY a.[index] ORDER BY ABS(a.Value - b.value), b.[index]) AS a_match_rank
            , RANK() OVER(PARTITION BY b.[index] ORDER BY ABS(a.Value - b.value), a.[index]) AS b_match_rank
        FROM dbo.TableA AS a
        CROSS JOIN dbo.TableB AS b
    )
    , best_matches AS (
        SELECT
              a_index
            , a_value
            , b_index
            , b_value
        FROM ranked_matches
        WHERE
                a_match_rank = 1
            AND b_match_rank= 1
    )
SELECT
      TableA.[index] AS a_index
    , TableA.value AS a_value
    , best_matches.b_index
    , best_matches.b_value
FROM dbo.TableA
LEFT JOIN best_matches ON
    best_matches.a_index = TableA.[index]
ORDER BY
    TableA.[index];

BEARBEITEN:

Obwohl dieses Verfahren CTEs verwendet, wird keine Rekursion verwendet und ist daher nicht auf 32K-Rekursionen beschränkt. Aus Performance-Sicht könnte hier aber noch Luft nach oben sein.