SQL join query to show rows with non-existent rows in one table

Thank you for SQLfiddle and sample data! I wish more questions started this way.

If you want all members regardless of whether they have an entry for that date, you want a LEFT OUTER JOIN. You were very close with this version however a little trick with outer joins is that if you add a filter to the outer table in the WHERE clause, you turn an outer join to an inner join, because it will exclude any rows that are NULL on that side (because it doesn't know if NULL would match the filter or not).

I modified the first query to get a row for every member:

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

I'll leave it as an exercise for the reader to take it from there and add the other columns, formatting, COALESCE etc.

Some other notes:

  • please always use the schema prefix when creating and referencing objects

  • please always use a length when converting to varchar etc.

  • stay away from ambiguous, regional date formats like mm-dd-yyyy

  • consider using aliases to make your queries easier to read. E.g. the above could be re-written as:

    SELECT m.Member_ID
      ,t.Date_Start
      ,t.Hours_Actual
      ,t.Hours_Bill
    FROM dbo.Members AS m
    LEFT OUTER JOIN dbo.Time_Entry AS t
    ON m.Member_ID = t.Member_ID
    AND t.Date_Start = '20131110';
    

    ... a lot tidier, IMHO, as long as you use sensible aliases.


When I've been faced with this type of problem in the past, I have created a "numbers" table to help deal with the missing rows.

I created my numbers table specifically to deal with dates as so:

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

This creates a table with a single row for each and every date between 1900-01-01 and 2099-12-31. I use TOP(73049) to limit the date range generated in my example to thos dates - if you work with a different date range, you could adjust that number.

Next, I add the dDates table to my query so that a row is returned for every date in the desired range for every member_id. The result is then joined to the Time_Entry table as such:

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

This allows you to specify a date range for the report.

You can further refine the results by adding COALESCE(...) and SUM(...) as per:

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

This results in the following output for your sample data:

enter image description here