SQL连接查询以显示一个表中不存在行的行


12

我正在尝试完成一些有关员工时间记录的报告。

我们有两个专门针对此问题的表格。Members表中列出了员工,他们每天输入他们已执行的工作的时间条目并将其存储在Time_Entry表中。

使用SQL Fiddle进行设置的示例:http ://sqlfiddle.com/#!3/e3806/7

最终的结果我要的是一个表,表示所有Members列中的列表,然后将展示他们的总和小时,在其他列查询的日期。

问题似乎是,如果Time_Entry表中没有特定成员的行,那么该成员现在将有一行。我尝试了几种不同的联接类型(左,右,内部,外部,完全外部等),但似乎没有一种能满足我的要求(基于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

查询11-1的特定日期时,我现在得到什么:

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

根据日期为2013年10月10日的EGirsch的一个“时间输入”行,这是正确的,但是我需要为其他成员看到零才能获取报告,并最终获得此信息的Web仪表板/报告。

这是我的第一个问题,当我搜索Join查询等时。老实说,我不确定该函数的名称,所以我希望这不是重复的,也可以帮助其他人寻找解决方案。类似的问题。

Answers:


11

感谢您提供SQLfiddle和示例数据!我希望以这种方式开始更多的问题。

如果希望所有成员,无论他们是否有该日期的条目,都需要一个LEFT OUTER JOIN。您与该版本非常接近,但是外部联接的一个小技巧是,如果在WHERE子句中向外部表添加过滤器,则将外部联接转换为内部联接,因为它将排除NULL该侧的任何行(因为它不知道是否NULL匹配过滤器)。

我修改了第一个查询,为每个成员获取一行:

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

我将其保留为练习,以使读者从那里开始使用它并添加其他列,格式COALESCE等。

其他注意事项:


亚伦,非常感谢您的反馈。SQL新手在这里,和不知道的区别WHEREAND。我最初使用别名,但是sqlfiddle似乎不喜欢它,所以我只使用了完整格式。也感谢其他SQL技巧。您会建议ISNULL还是COALESCE将数据设为0而不是NULL?再次感谢!
farewelldave 2013年

1
@farewelldave我更喜欢COALESCE,因为它是标准的,并且没有偏离其他语言的功能(例如,比较ISNULL在SQL Server与VB中的工作方式)。在几乎所有情况下,性能差异都是无关紧要的,只有一种除外。这里有更多细节
亚伦·伯特兰

4

过去遇到这类问题时,我创建了一个“数字”表来帮助处理丢失的行。

我专门创建了数字表来处理日期:

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

这将创建一个表,其中包含1900-01-01和2099-12-31之间的每个日期的单行。我曾经将TOP(73049)示例中生成的日期范围限制为日期-如果您使用其他日期范围,则可以调整该数字。

接下来,将dDates表添加到查询中,以便在每个日期的期望范围内为每个日期返回一行member_id。然后将结果按如下方式连接到Time_Entry表:

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

这使您可以指定报告的日期范围。

您可以通过添加COALESCE(...)和来进一步细化结果SUM(...)

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

这将为您的示例数据提供以下输出:

在此处输入图片说明


谢谢,马克斯。通过搜索“计数表”而不是“数字表”,可以找到有关此技术的大量信息。它们非常适合通过将使用游标/循环的操作转换为使用集合的操作来提高性能。关系数据库更喜欢集合。
Suncat2000

1
@ Suncat2000-同意,尽管我更喜欢使用“数字表”这个名称,因为理所当然地意味着加法,根据我的经验,这种模式很少用于数学运算。它们在很多方面都很有用,但是可以肯定的是,可以获得的最大性能改进之一是通过使用数字表从RBAR方法变为基于集的方法。
Max Vernon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.