我有一个涉及三个表的查询:
Employee
Attendance
Category
,其中
Employee
的PK为Id
; Category
的PK为Staff_id
; Attendance
的PK为attendance_Id
; Employee
有一个外键Staff
引用Category.Staff_id
; Attendance
有一个引用Id
Employee.Id
我需要修改我的查询,以提供从第四个表position
中提取的其他列Position
,并按Position.position
和Employee.Staff
对结果进行分组。我无法修改任何表格的结构或内容。
结果行应如下所示,其中" Driver"对应Staff = 2
:
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
如何修改查询以产生所需的结果?
答案 0 :(得分:0)
我希望你原谅我说你原来的原始查询非常可怕。它统一执行子查询,其中连接更合适,并且它有多个子查询,这些子查询要求作为公共表表达式进行分解,或者甚至简单地作为顶级聚合。它还表示一些WHERE
谓词,这些谓词完全是对基表的外键约束的冗余。它使用不透明的表别名而不是有意义的表别名。
原始查询也有一些非常可疑的结构:
子查询C
和D
均从表Employee
中选择,但所选列的 none 实际上来自该表。所有这些都是不相关的聚合(子)查询的结果,因此子查询C
和D
将分别提供与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
的整点)。另请注意,如果1
和2
是Employee.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
分组。