PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Unisciti a quattro tabelle che coinvolgono LEFT JOIN senza duplicati

Hai due LEFT JOINS :

  • Il primo join a sinistra può unire a più righe da solved . Dì, "jane" e "luke" hanno risolto il compito.
  • Il secondo join a sinistra può unirsi solo agli utenti di nome 'luke' ('luke' nella condizione di join!).

Ottieni ancora entrambi righe, 'jane' non viene mostrata, la condizione di unione la filtra, ma LEFT JOINS conserva comunque la riga nel risultato e aggiunge valori NULL.

Puoi ottenere ciò che cerchi utilizzando le parentesi e un [INNER] JOIN invece di LEFT JOINS tra solved e users . Il manuale:

Utilizzare le parentesi se necessario per determinare l'ordine di annidamento. In assenza di parentesi, JOIN s annida da sinistra a destra.

SELECT c.name AS cat_name, t.name AS task_name, u.name AS user_name
FROM   task t
JOIN   category c ON cat.id = t.category_id
LEFT   JOIN
      (solved s JOIN users u ON u.id = s.user_id AND u.name = 'luke') ON s.task_id = t.id
ORDER  BY 1, 2, 3;
  • Usando il nome della tabella users al posto della parola riservata user .

  • Supponendo che users.name è definito unico oppure puoi avere più utenti chiamati 'luke'.

  • Se (task.id, users.id) in solved è definito UNIQUE o PRIMARY KEY , non hai bisogno di DISTINCT affatto.

La query risultante non è solo corretta, ma anche più veloce.

Versione SQLAlchemy della query precedente: (contributo di @van)
Ciò presuppone che Category , Task e User sono classi mappate, mentre solved è un'istanza di Table (solo una tabella di associazione come mostrato nell'esempio di codice Many to Many):

user_name = 'luke'
q = (session.query(Category.name, Task.name, User.name)
     .select_from(Task)
     .join(Category)
     .outerjoin(
         join(solved, User,
              (solved.c.user_id == User.id) & (User.name == user_name),
         ))
     .order_by(Category.name, Task.name, User.name)
     )