web-dev-qa-db-fra.com

Postgres: jointure gauche avec ordre par et limite 1

J'ai la situation:

Table1 has a list of companies.
Table2 has a list of addresses.
Table3 is a N relationship of Table1 and Table2, with fields 'begin' and 'end'.

Étant donné que les entreprises peuvent évoluer au fil du temps, une jointure à gauche parmi elles entraîne plusieurs enregistrements pour chaque entreprise.

Les champs begin et end ne sont jamais NULL. La solution pour trouver la dernière adresse est d'utiliser un ORDER BY being DESC, et pour supprimer les anciennes adresses est un LIMIT 1.

Cela fonctionne bien si la requête ne peut amener qu'une seule entreprise. Mais j'ai besoin d'une requête qui apporte tous les enregistrements Table1, joints à leurs adresses Table2 actuelles. Par conséquent, la suppression des données obsolètes doit être effectuée (AFAIK) dans la clause ON de LEFT JOIN.

Une idée comment je peux construire la clause pour ne pas créer de sociétés Table1 dupliquées et apporter la dernière adresse?

19
Hikari

J'ai réussi à le résoudre en utilisant la fonction Windows:

WITH ranked_relationship AS(
    SELECT
        *
        ,row_number() OVER (PARTITION BY fk_company ORDER BY dt_start DESC) as dt_last_addr
    FROM relationship
)

SELECT
    company.*
    address.*,
    dt_last_addr as dt_relationship
FROM
    company
    LEFT JOIN ranked_relationship as relationship
            ON relationship.fk_company = company.pk_company AND dt_last_addr = 1
    LEFT JOIN address ON address.pk_address = relationship.fk_address

row_number () crée un compteur int pour chaque enregistrement, à l'intérieur de chaque fenêtre basée sur fk_company. Pour chaque fenêtre, l'enregistrement avec la dernière date vient d'abord avec le rang 1, puis dt_last_addr = 1 s'assure que le JOIN ne se produit qu'une seule fois pour chaque fk_company, avec l'enregistrement avec la dernière adresse.

Les fonctions de fenêtre sont très puissantes et peu de personnes les utilisent, elles évitent de nombreuses jointures et sous-requêtes complexes!

6
Hikari

Utilisez une sous-requête dépendante avec la fonction max () dans une condition de jointure.
Quelque chose comme dans cet exemple:

SELECT *
FROM companies c
LEFT JOIN relationship r
ON c.company_id = r.company_id
   AND r."begin" = (
        SELECT max("begin")
        FROM relationship r1
        WHERE c.company_id = r1.company_id
     )
INNER JOIN addresses a
ON a.address_id = r.address_id 

démo: http://sqlfiddle.com/#!15/f80c6/2

19
krokodilko