web-dev-qa-db-fra.com

Jointure gauche sans lignes en double

J'ai deux tables appelées record et record_history. Pour chaque enregistrement, il peut y avoir plusieurs historiques. Ils peuvent être joints par id et record_id. Je veux obtenir toutes les entrées de record avec les dernières record_history Les données. J'ai créé la requête comme,

SELECT rec.id, rec.name, rech1.data AS last_history_data
FROM record rec
LEFT OUTER JOIN record_history rech1 ON (rec.id = rech1.record_id)
LEFT OUTER JOIN record_history rech2 ON (rec.id = rech2.record_id AND rech2.ts > rech1.ts)
WHERE rech2.id IS NULL
ORDER BY rec.id DESC

Ici, je reçois le dernier par ts. Cela fonctionne tant qu'il n'y a pas d'entrées ts en double. Si l'horodatage récent est répété dans record_history, cette requête renvoie plus d'une ligne pour un enregistrement. Comment pouvons-nous appliquer la limite ici sur la jointure de gauche pour restreindre les lignes en double?

8
RaR

Sauf si vous êtes dans une version très ancienne de Postgres, vous n'avez pas besoin de la double jointure. Vous pouvez obtenir le même résultat en utilisant une LATERAL join.

Les résultats en double peuvent être évités dans votre méthode en ajoutant une deuxième condition en plus de rec.id = rech2.record_id. Avec la méthode de jointure LATERAL, l'utilisation de LIMIT l'évite de toute façon. Il ne peut y avoir qu'une seule ligne renvoyée par la sous-requête latérale. Nous pouvons ajouter une deuxième condition pour que le choix soit déterministe (à partir des deux lignes ou plus avec le même horodatage):

SELECT rec.id, rec.name, rech.data AS last_history_data
FROM record AS rec
     LEFT OUTER JOIN LATERAL
     ( SELECT rech.data
       FROM record_history AS rech
       WHERE rec.id = rech.record_id
       ORDER BY rech.ts DESC
                -- ,rech.id DESC               -- optional
       LIMIT 1 
     ) AS rech
     ON TRUE
ORDER BY rec.id DESC ;

En ce qui concerne la façon de procéder avec la méthode d'origine (2 jointures et IS NULL check), vous pouvez modifier la condition ON - en supposant qu'il existe une colonne id dans la table d'historique afin que (id) ou au moins (ts, id) est unique:

LEFT OUTER JOIN record_history rech2 
ON rec.id = rech2.record_id 
   AND (rech2.ts > rech1.ts OR rech2.ts = rech1.ts AND rech2.id > rech1.id)

Soit dit en passant, vous pouvez remplacer cette deuxième jointure LEFT et IS NULL vérifier avec un NOT EXISTS sous-requête avec les mêmes résultats et éventuellement une efficacité similaire (ou même avec un NOT IN sous-requête bien que cela nécessite des précautions supplémentaires pour les colonnes nullables, non recommandé).

11
ypercubeᵀᴹ