web-dev-qa-db-fra.com

Requête de jointure SQL pour afficher des lignes avec des lignes inexistantes dans une table

J'essaie d'obtenir des rapports pour les enregistrements de temps des employés.

Nous avons deux tableaux spécifiquement pour cette question. Les employés sont répertoriés dans le tableau Members et chaque jour, ils saisissent les entrées d'heure du travail qu'ils ont effectué et sont stockés dans le Time_Entry table.

Exemple de configuration avec SQL Fiddle: http://sqlfiddle.com/#!3/e3806/7

Le résultat final que je veux est un tableau qui montre [~ # ~] tous [~ # ~] le Members dans une liste de colonnes, puis affichera leur somme d'heures pour la date interrogée dans les autres colonnes.

Le problème semble être que s'il n'y a pas de ligne dans le Time_Entry table pour un membre particulier, il y a maintenant une ligne pour ce membre. J'ai essayé plusieurs types de jointures différents (gauche, droite, intérieure, extérieure, pleine extérieure, etc.) mais aucun ne semble me donner ce que je veux, ce qui serait (basé sur le dernier exemple dans 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

Ce que j'obtiens actuellement lorsque je demande une date précise de 11-1:

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

Ce qui est correct sur la base d'une ligne d'entrée unique datée du 11-10-2013 pour EGirsch, mais j'ai besoin de voir des zéros pour les autres membres afin d'obtenir des rapports et, éventuellement, un tableau de bord/rapport Web pour ces informations.

Ceci est ma première question, et pendant que je cherchais des requêtes Join, etc. Je ne sais vraiment pas comment cette fonction pourrait être appelée, donc j'espère que ce n'est pas un doublon et aidera les autres à essayer trouver une solution à des problèmes similaires.

12
farewelldave

Merci pour SQLfiddle et des exemples de données! J'aimerais que d'autres questions commencent de cette façon.

Si vous voulez que tous les membres aient une entrée pour cette date, vous voulez un LEFT OUTER JOIN. Vous étiez très proche de cette version cependant une petite astuce avec les jointures externes est que si vous ajoutez un filtre à la table externe dans la clause WHERE, vous transformez une jointure externe en une jointure interne join, car il exclura toutes les lignes qui sont NULL de ce côté (car il ne sait pas si NULL correspondrait ou non au filtre).

J'ai modifié la première requête pour obtenir une ligne pour chaque membre:

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

Je vais le laisser comme un exercice pour que le lecteur le prenne à partir de là et ajoute les autres colonnes, le formatage, COALESCE etc.

Quelques autres notes:

11
Aaron Bertrand

Lorsque j'ai été confronté à ce type de problème dans le passé, j'ai créé une table "nombres" pour aider à gérer les lignes manquantes.

J'ai créé ma table de nombres spécifiquement pour traiter les dates comme suit:

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

Cela crée un tableau avec une seule ligne pour chaque date entre 1900-01-01 et 2099-12-31. J'utilise TOP(73049) pour limiter la plage de dates générée dans mon exemple à ces dates - si vous travaillez avec une plage de dates différente, vous pouvez ajuster ce nombre.

Ensuite, j'ajoute la table dDates à ma requête afin qu'une ligne soit renvoyée pour chaque date dans la plage souhaitée pour chaque member_id. Le résultat est ensuite joint à la table Time_Entry En tant que tel:

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

Cela vous permet de spécifier une plage de dates pour le rapport.

Vous pouvez affiner davantage les résultats en ajoutant COALESCE(...) et SUM(...) selon:

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

Il en résulte la sortie suivante pour vos exemples de données:

enter image description here

4
Max Vernon