sql >> Base de Datos >  >> RDS >> PostgreSQL

Combinación externa izquierda actuando como combinación interna

La consulta probablemente se puede simplificar a:

SELECT u.name AS user_name
     , p.name AS project_name
     , tl.created_on::date AS changeday
     , coalesce(sum(nullif(new_value, '')::numeric), 0)
     - coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
FROM   users             u
LEFT   JOIN (
        tasks            t 
   JOIN fixins           f  ON  f.id = t.fixin_id
   JOIN projects         p  ON  p.id = f.project_id
   JOIN task_log_entries tl ON  tl.task_id = t.id
                           AND  tl.field_id = 18
                           AND (tl.created_on IS NULL OR
                                tl.created_on >= '2013-09-08' AND
                                tl.created_on <  '2013-09-09') -- upper border!
       ) ON t.assignee_id = u.id
WHERE  EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
GROUP  BY 1, 2, 3
ORDER  BY 1, 2, 3;

Esto devuelve todos los usuarios que alguna vez han tenido alguna tarea.
Más datos por proyectos y día donde existen datos en el intervalo de fechas especificado en task_log_entries .

Puntos principales

  • La función agregada sum() ignora NULL valores. COALESCE() por fila ya no se requiere tan pronto como reformule el cálculo como la diferencia de dos sumas:

     ,coalesce(sum(nullif(new_value, '')::numeric), 0) -
      coalesce(sum(nullif(old_value, '')::numeric), 0) AS hours
    

    Sin embargo, si es posible que todos las columnas de una selección tienen NULL o cadenas vacías, envuelva las sumas en COALESCE una vez.
    Estoy usando numeric en lugar de float , alternativa más segura para minimizar los errores de redondeo.

  • Su intento de obtener valores distintos de la combinación de users y tasks es inútil, ya que te unes a task una vez más más abajo. Aplane toda la consulta para que sea más simple y rápida.

  • Estos referencias posicionales son solo una conveniencia notacional:

    GROUP BY 1, 2, 3
    ORDER BY 1, 2, 3
    

    ... haciendo lo mismo que en su consulta original.

  • Para obtener una date de una timestamp simplemente puede transmitir a date :

    tl.created_on::date AS changeday
    

    Pero es mucho mejor probar con valores originales en WHERE cláusula o JOIN condición (si es posible, y es posible aquí), para que Postgres pueda usar índices simples en la columna (si está disponible):

     AND (tl.created_on IS NULL OR
          tl.created_on >= '2013-09-08' AND
          tl.created_on <  '2013-09-09')  -- next day as excluded upper border
    

    Tenga en cuenta que un literal de fecha se convierte en una timestamp a las 00:00 del día en su hora actual zona . Tienes que elegir el siguiente día y excluir como borde superior. O proporcione un literal de marca de tiempo más explícito como '2013-09-22 0:0 +2':: timestamptz . Más información sobre la exclusión del borde superior:

  • Para el requisito every user who has ever been assigned to a task agrega el WHERE cláusula:

    WHERE EXISTS (SELECT 1 FROM tasks t1 WHERE t1.assignee_id = u.id)
    
  • Lo más importante :UN LEFT [OUTER] JOIN conserva todas las filas a la izquierda de la unión. Agregando un WHERE cláusula a la derecha tabla puede anular este efecto. En su lugar, mueva la expresión de filtro a JOIN cláusula. Más explicación aquí:

  • Paréntesis se puede utilizar para forzar el orden en que se unen las tablas. Rara vez se necesita para consultas simples, pero es muy útil en este caso. Uso la función para unirme a task , fixins , projects y task_log_entries antes de unirlo todo a users - sin subconsulta.

  • Alias ​​de tabla facilita la escritura de consultas complejas.