Query di join SQL per mostrare le righe con righe inesistenti in una tabella


12

Sto cercando di ottenere alcuni rapporti per i registri orari dei dipendenti.

Abbiamo due tabelle specifiche per questa domanda. I dipendenti sono elencati nella Memberstabella e ogni giorno inseriscono le voci del lavoro che hanno svolto e che sono archiviate nella Time_Entrytabella.

Esempio di installazione con SQL Fiddle: http://sqlfiddle.com/#!3/e3806/7

Il risultato finale che sto cercando è una tabella che mostra TUTTI i Membersin un elenco di colonne e quindi mostrerà le loro ore di somma per la data richiesta nelle altre colonne.

Il problema sembra essere che se non vi sono righe nella Time_Entrytabella per un determinato membro, ora esiste una riga per quel membro. Ho provato diversi tipi di join (Left, Right, Inner, Outer, Full Outer, ecc.), Ma nessuno sembra darmi quello che voglio, che sarebbe (basato sull'ultimo esempio in 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

Cosa ricevo attualmente quando eseguo una query per una data specifica dell'11-1:

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

Il che è corretto in base alla riga di una registrazione temporizzata datata 11-10-2013 per EGirsch, ma ho bisogno di vedere zeri per gli altri membri per ottenere report e infine un dashboard / report Web per queste informazioni.

Questa è la mia prima domanda, e mentre cercavo le query Join, ecc. Non sono sinceramente sicuro di come si possa chiamare questa funzione, quindi spero che questa non sia una duplicazione e aiuterà anche gli altri a cercare una soluzione per problemi simili.

Risposte:


11

Grazie per SQLfiddle e dati di esempio! Vorrei che altre domande iniziassero in questo modo.

Se vuoi tutti i membri indipendentemente dal fatto che abbiano una voce per quella data, vuoi un LEFT OUTER JOIN. Sei stato molto vicino a questa versione, tuttavia un piccolo trucco con i join esterni è che se aggiungi un filtro alla tabella esterna nella WHEREclausola, trasformi un join esterno in un join interno, poiché escluderà tutte le righe che si trovano NULLsu quel lato (perché non sa se NULLcorrisponderebbe o meno al filtro).

Ho modificato la prima query per ottenere una riga per ogni membro:

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

Lascio che sia un esercizio per il lettore prenderlo da lì e aggiungere le altre colonne, la formattazione, COALESCEecc.

Alcune altre note:


Aaron, grazie mille per il feedback. Newbie di SQL qui, e non aveva idea della differenza tra WHEREe AND. All'inizio avevo usato gli alias, ma a sqlfiddle non sembrava piacermi, quindi sono passato al formato completo. Grazie anche per gli altri suggerimenti SQL. Consiglieresti ISNULLo COALESCErendere i dati 0 invece di NULL? Grazie ancora!
addio

1
@farewelldave Preferisco COALESCE perché è standard e non si discosta dalla sua funzionalità in altre lingue (confronta ad esempio il funzionamento di ISNULL in SQL Server rispetto a VB). In quasi tutti i casi la differenza di prestazioni è irrilevante, tranne una. Molti più dettagli qui .
Aaron Bertrand

4

Quando in passato ho riscontrato questo tipo di problema, ho creato una tabella "numeri" per aiutare a gestire le righe mancanti.

Ho creato la mia tabella dei numeri specificamente per gestire le date così:

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

Questo crea una tabella con una singola riga per ciascuna data tra il 1900-01-01 e il 2099-12-31. Uso TOP(73049)per limitare l'intervallo di date generato nel mio esempio a queste date: se lavori con un intervallo di date diverso, puoi regolare quel numero.

Successivamente, aggiungo la dDatestabella alla mia query in modo che venga restituita una riga per ogni data nell'intervallo desiderato per ogni member_id. Il risultato viene quindi unito alla Time_Entrytabella come tale:

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

Ciò consente di specificare un intervallo di date per il rapporto.

È possibile perfezionare ulteriormente i risultati aggiungendo COALESCE(...)e SUM(...)secondo:

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

Ciò comporta il seguente output per i dati di esempio:

inserisci qui la descrizione dell'immagine


Grazie Max. Puoi trovare molte informazioni su questa tecnica cercando "tabella tally" anziché "tabella numerica". Sono ottimi per migliorare le prestazioni convertendo le operazioni usando cursori / loop in operazioni usando set. I database relazionali preferiscono gli insiemi.
Suncat2000,

1
@ Suncat2000 - concordato, anche se preferisco il nome "tabella dei numeri" poiché il conteggio implica addizione e, nella mia esperienza, questo schema è usato raramente per operazioni matematiche. Sono ottimi per molte cose, ma certamente uno dei maggiori miglioramenti delle prestazioni che puoi ottenere è passare da un approccio RBAR, a un approccio basato su set, usando una tabella numerica.
Max Vernon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.