繁体   English   中英

嵌套选择,联合,左联接

[英]NESTED SELECT, UNION, LEFT JOIN

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

  1. Employee
  2. Attendance
  3. Category

哪里

  • Employee PK为Id ;
  • Category的PK为Staff_id ;
  • 的PK Attendanceattendance_Id ;
  • Employee具有引用Category.Staff_id的外键Staff
  • Attendance具有引用Employee.Id的外键Id

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

结果行应如下所示,其中“驱动程序”对应于Staff = 2

职位| TotalEmp | 总出勤| TimeIn | 暂停

 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

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

希望您能原谅我说您的原始原始查询非常可怕。 它统一执行更适合连接的子查询,并且它具有多个子查询,这些子查询应作为公共表表达式甚至是顶级聚合被分解出来。 它还表达了一些WHERE谓词,这些谓词与基表上的外键约束完全无关。 它使用不透明的表别名代替有意义的表别名。

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

  • 子查询CD各自从表Employee中进行选择,但所选列实际上都不来自该表。 所有这些都是不相关的聚合(子)查询的结果,因此子查询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分组也不会获得任何收益。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM