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


12

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 Memberstableau et chaque jour, ils saisissent les entrées de temps du travail qu'ils ont effectué et sont stockés dans le Time_Entrytableau.

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 les Membersdans une liste de colonnes et ensuite montrera leurs heures de somme pour la date interrogée dans les autres colonnes.

Le problème semble être que s'il n'y a pas de ligne dans la Time_Entrytable 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 suis honnêtement pas sûr de ce que cette fonction pourrait être appelée, donc j'espère que ce n'est pas un doublon et aidera les autres aussi à essayer de trouver une solution problèmes similaires.

Réponses:


11

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

Si vous voulez que tous les membres aient ou non une entrée pour cette date, vous voulez a LEFT OUTER JOIN. Vous étiez très proche de cette version mais une petite astuce avec les jointures externes est que si vous ajoutez un filtre à la table externe dans la WHEREclause, vous transformez une jointure externe en jointure interne, car elle exclura toutes les lignes qui se trouvent NULLde ce côté (car il ne sait pas si NULLcela 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, la mise en forme, COALESCEetc.

Quelques autres notes:


Aaron, merci beaucoup pour vos commentaires. Débutant SQL ici, et n'avait aucune idée de la différence entre WHEREet AND. J'avais utilisé des alias à l'origine, mais sqlfiddle ne semblait pas l'aimer, alors je suis simplement passé au format complet. Merci également pour les autres conseils SQL. Recommanderiez-vous ISNULLou COALESCEpour rendre les données 0 au lieu de NULL? Merci encore!
adieu

1
@farewelldave Je préfère COALESCE car il est standard et ne s'écarte pas de ses fonctionnalités dans d'autres langues (comparez comment ISNULL fonctionne dans SQL Server vs VB, par exemple). Dans presque tous les cas, la différence de performances est sans conséquence, sauf un. Beaucoup plus de détails ici .
Aaron Bertrand

4

Lorsque j'ai été confronté à ce type de problème dans le passé, j'ai créé un tableau de "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 dDatestable à 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 Time_Entrytable 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:

entrez la description de l'image ici


Merci, Max. Vous pouvez trouver de nombreuses informations sur cette technique en recherchant "table de pointage" au lieu de "table de nombres". Ils sont parfaits pour améliorer les performances en convertissant les opérations utilisant des curseurs / boucles en opérations utilisant des ensembles. Les bases de données relationnelles préfèrent les ensembles.
Suncat2000

1
@ Suncat2000 - d'accord, même si je préfère le nom "table des nombres" car le décompte implique l'addition, et d'après mon expérience, ce modèle est rarement utilisé pour les opérations mathématiques. Ils sont parfaits pour de nombreuses choses, mais certainement l'une des plus grandes améliorations de performances que vous pouvez obtenir passe d'une approche RBAR à une approche basée sur des ensembles en utilisant une table de nombres.
Max Vernon
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.