Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Jointure gauche avec la valeur la plus proche sans doublons

Vous trouverez ci-dessous une solution basée sur des ensembles utilisant des CTE et des fonctions de fenêtrage.

Les ranked_matches CTE attribue un rang de correspondance le plus proche pour chaque ligne dans TableA ainsi qu'un rang de correspondance le plus proche pour chaque ligne dans TableB , en utilisant l'index valeur en tant que bris d'égalité.

Les best_matches CTE renvoie les lignes de ranked_matches qui ont le meilleur rang (valeur de rang 1) pour les deux classements.

Enfin, la requête externe utilise un LEFT JOIN de TableA aux best_matches CTE pour inclure le TableA les lignes auxquelles une meilleure correspondance n'a pas été attribuée car la correspondance la plus proche est déjà attribuée.

Notez que cela ne renvoie pas de correspondance pour la ligne Index 3 TableA indiquée dans les résultats de votre exemple. La correspondance la plus proche pour cette ligne est l'index 3 de la TableB, soit une différence de 83. Cependant, cette ligne de la TableB est plus proche de la ligne de l'index 2 de la TableA, soit une différence de 14, elle a donc déjà été attribuée. Veuillez clarifier votre question si ce n'est pas ce que vous voulez. Je pense que cette technique peut être modifiée en conséquence.

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];

MODIF :

Bien que cette méthode utilise des CTE, la récursivité n'est pas utilisée et n'est donc pas limitée aux récursions 32K. Cependant, il peut y avoir place à amélioration ici du point de vue des performances.