NESTED SELECT,UNION,LEFT JOIN

时间:2015-03-13 09:36:43

标签: sql sql-server

我有一个涉及三个表的查询:

  1. Employee
  2. Attendance
  3. Category
  4. ,其中

    • Employee的PK为Id;
    • Category的PK为Staff_id;
    • Attendance的PK为attendance_Id;
    • Employee有一个外键Staff引用Category.Staff_id;
    • Attendance有一个引用Id
    • 的外键Employee.Id

    我需要修改我的查询,以提供从第四个表position中提取的其他列Position,并按Position.positionEmployee.Staff对结果进行分组。我无法修改任何表格的结构或内容。

    结果行应如下所示,其中" Driver"对应Staff = 2

    职位| TotalEmp | TotalAttendance | TimeIn | TimeOut

     Driver           5               5               8.00am       6.00pm 
    

    这是我当前的查询:

    SELECT D.TotalEmp, D.TotalAttendance, D.Timein, D.TimeOut
    FROM (
      SELECT B.TotalEmp, B.TimeIn, B.TimeOut FROM (
        SELECT
          (SELECT COUNT (distinct Id) FROM Employee WHERE Staff = 2) AS TotalEmp,
          (
            SELECT COUNT(id)
            FROM Attendance Q
            WHERE
              id IN (SELECT (Id) FROM Employee WHERE Staff = 2) 
              AND  CONVERT(datetime, CONVERT(nvarchar(10), Q.timeInDate, 103), 103) = '20/11/2014'
          ) AS TotalAttendance, 
          (
            SELECT MIN(CONVERT(VARCHAR(8),I.timeInDate,108))
            FROM Attendance I
            WHERE
              CONVERT(datetime, CONVERT(nvarchar(10), I.timeInDate, 103), 103) = '20/11/2014'
              AND I.id IN (SELECT (Id) FROM Employee WHERE Staff = 2)
          ) Timein,
          (
            SELECT
              MAX(CONVERT(VARCHAR(8),O.timeOutDate,108))
            FROM Attendance O
            WHERE
              CONVERT(datetime, CONVERT(nvarchar(10), O.timeOutDate, 103), 103) = '20/11/2014'
              AND O.id IN (SELECT (Id) FROM Employee WHERE Staff = 2)
          ) TimeOut
        FROM Employee
        WHERE Id IN (SELECT (id) FROM Attendance) 
      ) B 
    
      UNION
    
      SELECT C.TotalEmp, C.Time, C.TimeOut FROM (
        SELECT
          (SELECT COUNT (distinct Id) FROM Employee WHERE Staff = 1) AS TotalEmployee, 
          ( 
            SELECT COUNT(id)
            FROM Attendance R
            WHERE
              id IN (SELECT (Id) FROM Employee WHERE Staff = 1) 
              AND CONVERT(datetime, CONVERT(nvarchar(10), R.timeInDate, 103), 103) = '20/11/2014'
          ) AS TotalAttendance,
          (
            SELECT MIN(CONVERT(VARCHAR(8), T.timeInDate, 108))
            FROM Attendance T
            WHERE
              CONVERT(datetime, CONVERT(nvarchar(10), T.timeInDate, 103), 103) = '20/11/2014'
              AND T.id IN (SELECT (Id) FROM Employee WHERE Staff = 1)
          ) Timein,
          (
            SELECT MAX(CONVERT(VARCHAR(8),X.timeOutDate,108))
            FROM Attendance X
            WHERE
              CONVERT(datetime, CONVERT(nvarchar(10), X.timeOutDate, 103), 103) = '20/11/2014'
              AND X.id IN (SELECT (Id) FROM Employee WHERE Staff = 1)
          ) TimeOut
        FROM Employee
        WHERE Id IN (SELECT (id) FROM Attendance) 
      ) C
    ) D
    
    GROUP BY D.TotalEmp, D.TotalAttendance, D.Timein, D.TimeOut
    

    如何修改查询以产生所需的结果?

1 个答案:

答案 0 :(得分:0)

我希望你原谅我说你原来的原始查询非常可怕。它统一执行子查询,其中连接更合适,并且它有多个子查询,这些子查询要求作为公共表表达式进行分解,或者甚至简单地作为顶级聚合。它还表示一些WHERE谓词,这些谓词完全是对基表的外键约束的冗余。它使用不透明的表别名而不是有意义的表别名。

原始查询也有一些非常可疑的结构:

  • 子查询CD均从表Employee中选择,但所选列的 none 实际上来自该表。所有这些都是不相关的聚合(子)查询的结果,因此子查询CD将分别提供与Employee行一样多的行,所有行都相同(每个子查询)。当UNION运算符消除重复行时,将再次删除所有不需要的重复项。

  • 您在最外层查询中有GROUP BY子句,但在该查询的选择列表中没有聚合函数。也许您希望ORDER BY代替这些列,但如果没有,则GROUP BY完全没用。

  • 您正在将日期转换为字符串以进行比较;对于平等比较而言,这并不一定是错误的,但效率低下。但是,对于大于和小于比较, 是错误的,因此与MIN()MAX()一起使用也是错误的。但是,它可以很好地运作,在某些情况下通过产生正确的结果来欺骗你。

  • 您执行两个具有相同结构的子查询UNION,仅在某些查询谓词中有所不同。这需要合并为一个查询。

通过简化原始查询,一定会有所帮助。看起来这会产生相同的数据,除了添加Staff列并且可能以不同的顺序:

SELECT
  emp.Staff,
  COUNT(DISTINCT emp.id) AS TotalEmp,
  COUNT(DISTINCT att.id) AS TotalAttendance,
  MIN(att.timeInDate) AS TimeIn,
  MAX(att.timeOutDate) AS TimeOut,
FROM
  Employee emp
  LEFT JOIN Attendance att ON att.Id = emp.Id
WHERE 
  CAST(att.timeInDate AS DATE) = CONVERT(DATE, '20/11/2014', 103)
  AND (emp.Staff = 1 OR emp.Staff = 2)
GROUP BY emp.Staff

请注意,它按Staff进行分组;这消除了对UNION的需要,同时仍然保留了每个工作人员的聚合值(实际上,这是GROUP BY的整点)。另请注意,如果12Employee.Staff唯一可能的值,或者您也可以获得其他值的结果,那么您可以简化进一步删除限制结果的WHERE条件仅限于那些值。

另请注意,您的Datetime值会转换为Date以剥离时间部分;这比将它们格式化为字符串要有效得多。您的文字日期字符串将转换为Date进行比较(使用格式103)。

这是一个更好的起点,因为数据的结构和分组的性质是明确的。而且它简单得多!现在,如果您想以不同方式拆分组,那么这很容易实现。

特别是这样的事情应该做你想做的事情:

SELECT
  pos.position AS position,
  COUNT(DISTINCT emp.id) AS TotalEmp,
  COUNT(DISTINCT att.id) AS TotalAttendance,
  MIN(att.timeInDate) AS TimeIn,
  MAX(att.timeOutDate) AS TimeOut,
FROM
  Employee emp
  JOIN Position pos ON emp.position_id = pos.positionId
  LEFT JOIN Attendance att ON att.Id = emp.Id
WHERE
  CAST(att.timeInDate AS DATE) = CONVERT(DATE, '20/11/2014', 103)
GROUP BY pos.position

这依赖于这样一个事实,即每个职位只与一个Staff值相关联,因此它也无法按Staff分组。