Consulta de unión SQL para mostrar filas con filas inexistentes en una tabla


12

Estoy tratando de hacer algunos informes para los registros de tiempo de los empleados.

Tenemos dos tablas específicamente para esta pregunta. Los empleados se enumeran en la Memberstabla y cada día ingresan las entradas de tiempo del trabajo que han realizado y se almacenan en la Time_Entrytabla.

Ejemplo de configuración con SQL Fiddle: http://sqlfiddle.com/#!3/e3806/7

El resultado final voy a es una tabla que muestra TODOS los Membersde una lista de columnas y luego mostrarán sus horas de suma de la fecha consultada en las otras columnas.

El problema parece ser que si no hay una fila en la Time_Entrytabla para un miembro en particular, ahora hay una fila para ese miembro. He probado varios tipos de unión diferentes (izquierda, derecha, interior, exterior, exterior completo, etc.) pero ninguno parece darme lo que quiero, que sería (basado en el último ejemplo en SQL Fiddle):

/*** Desired End Result ***/

Member_ID   | COUNTTime_Entry | TIMEENTRYDATE | SUMHOURS_ACTUAL | SUMHOURS_BILL
ADavis      | 0               | 11-10-2013    | 0               | 0
BTronton    | 0               | 11-10-2013    | 0               | 0
CJones      | 0               | 11-10-2013    | 0               | 0
DSmith      | 0               | 11-10-2013    | 0               | 0
EGirsch     | 1               | 11-10-2013    | 0.92            | 1
FRowden     | 0               | 11-10-2013    | 0               | 0

Lo que obtengo actualmente cuando consulto la fecha específica del 11-1:

Member_ID   | COUNTTime_Entry | TIMEENTRYDATE | SUMHOURS_ACTUAL | SUMHOURS_BILL
EGirsch     | 1               | 11-10-2013    | 0.92            | 1

Lo cual es correcto en función de la fila de entrada de tiempo con fecha 10-10-2013 para EGirsch, pero necesito ver ceros para los otros miembros para obtener informes y, finalmente, un tablero web / informe para esta información.

Esta es mi primera pregunta, y aunque busqué consultas de combinación, etc. Honestamente, no estoy seguro de cómo podría llamarse esta función, así que espero que esto no sea un duplicado y ayude a otros a tratar de encontrar una solución para problemas similares

Respuestas:


11

¡Gracias por SQLfiddle y datos de muestra! Deseo que más preguntas comiencen de esta manera.

Si desea que todos los miembros, independientemente de si tienen una entrada para esa fecha, desea un LEFT OUTER JOIN. Estuviste muy cerca de esta versión, sin embargo, un pequeño truco con las combinaciones externas es que si agregas un filtro a la tabla externa en la WHEREcláusula, conviertes una combinación externa en una interna, porque excluirá cualquier fila que esté NULLen ese lado (porque no sabe si NULLcoincidiría con el filtro o no).

Modifiqué la primera consulta para obtener una fila para cada miembro:

SELECT Members.Member_ID
      ,Time_Entry.Date_Start
      ,Time_Entry.Hours_Actual
      ,Time_Entry.Hours_Bill
FROM dbo.Members
  LEFT OUTER JOIN dbo.Time_Entry
--^^^^ changed from FULL to LEFT
  ON Members.Member_ID = Time_Entry.Member_ID
  AND Time_Entry.Date_Start = '20131110';
--^^^ changed from WHERE to AND

Lo dejaré como un ejercicio para que el lector lo tome de allí y agregue las otras columnas, formateo, COALESCEetc.

Algunas otras notas:


Aaron, muchas gracias por los comentarios. SQL novato aquí, y no tenía idea de la diferencia entre WHEREy AND. Originalmente había usado alias, pero a sqlfiddle no parecía gustarle, así que solo tomé el formato completo. Gracias por los otros consejos SQL también. ¿Recomendaría ISNULLo COALESCEhacer que los datos sean 0 en lugar de NULL? ¡Gracias de nuevo!
despedida

1
@farewelldave Prefiero COALESCE porque es estándar y no se desvía de su funcionalidad en otros idiomas (compárese cómo funciona ISNULL en SQL Server vs. VB, por ejemplo). En casi todos los casos, la diferencia de rendimiento es intrascendente, excepto uno. Muchos más detalles aquí .
Aaron Bertrand

4

Cuando me he enfrentado a este tipo de problema en el pasado, he creado una tabla de "números" para ayudar a lidiar con las filas que faltan.

Creé mi tabla de números específicamente para tratar las fechas de la siguiente manera:

CREATE TABLE Dates
(
    dDate DATETIME NOT NULL CONSTRAINT PK_Dates PRIMARY KEY CLUSTERED
);

INSERT INTO Dates (dDate)
SELECT TOP(73049) DATEADD(d, -1, ROW_NUMBER() OVER (ORDER BY o.object_id)) AS dDate
FROM master.sys.objects o, master.sys.objects o1, master.sys.objects o2

Esto crea una tabla con una sola fila para cada fecha entre 1900-01-01 y 2099-12-31. Solía TOP(73049)limitar el intervalo de fechas generado en mi ejemplo a esas fechas; si trabaja con un intervalo de fechas diferente, puede ajustar ese número.

A continuación, agrego la dDatestabla a mi consulta para que se devuelva una fila para cada fecha en el rango deseado para cada member_id. El resultado se une a la Time_Entrytabla como tal:

SELECT MD.Member_ID,
    MD.dDate,
    T.Date_Start,
    T.Hours_Actual,
    T.Hours_Bill
FROM 
    (
        SELECT M.Member_ID, D.dDate
        FROM dbo.Dates D, dbo.Members M
        WHERE D.dDate >= '20131110' AND D.dDate < '20131112'
    ) AS MD
    LEFT JOIN dbo.Time_Entry T ON MD.Member_ID = T.Member_ID AND MD.dDate = T.Date_Start
ORDER BY MD.Member_ID, MD.dDate

Esto le permite especificar un rango de fechas para el informe.

Puede refinar aún más los resultados agregando COALESCE(...)y SUM(...)según:

SELECT MD.Member_ID,
    MD.dDate,
    T.Date_Start,
    SUM(COALESCE(T.Hours_Actual, 0)) AS TotalHoursActual,
    SUM(COALESCE(T.Hours_Bill, 0)) AS TotalHoursBill
FROM 
    (
        SELECT M.Member_ID, D.dDate
        FROM dbo.Dates D, dbo.Members M
        WHERE D.dDate >= '20131110' AND D.dDate < '20131112'
    ) AS MD
    LEFT JOIN dbo.Time_Entry T ON MD.Member_ID = T.Member_ID AND MD.dDate = T.Date_Start
GROUP BY MD.Member_ID, MD.dDate, T.Date_Start
ORDER BY MD.Member_ID, MD.dDate

Esto da como resultado la siguiente salida para sus datos de muestra:

ingrese la descripción de la imagen aquí


Gracias Max. Puede encontrar una gran cantidad de información sobre esta técnica buscando "tabla de conteo" en lugar de "tabla de números". Son excelentes para mejorar el rendimiento al convertir operaciones usando cursores / bucles en operaciones usando conjuntos. Las bases de datos relacionales prefieren conjuntos.
Suncat2000

1
@ Suncat2000: de acuerdo, aunque prefiero el nombre "tabla de números", ya que el recuento implica la suma y, en mi experiencia, este patrón rara vez se usa para operaciones matemáticas. Son excelentes para muchas cosas, pero sin duda una de las mayores mejoras de rendimiento que puede obtener es pasar de un enfoque RBAR a un enfoque basado en conjuntos, utilizando una tabla de números.
Max Vernon
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.