如何使这个复杂的查询更有效?

时间:2017-01-17 16:49:43

标签: sql sql-server database

enter image description here

我想选择拥有10件以上产品且年龄超过50件的员工。我也希望选择最后一件产品。我使用以下查询:

SELECT 
    PE.EmployeeID, E.Name, E.Age, 
    COUNT(*) as ProductCount,
    (SELECT TOP(1) xP.Name 
     FROM ProductEmployee xPE 
     INNER JOIN Product xP ON xPE.ProductID = xP.ID 
     WHERE xPE.EmployeeID = PE.EmployeeID 
       AND xPE.Date = MAX(PE.Date)) as LastProductName
FROM 
    ProductEmployee PE 
INNER JOIN 
    Employee E ON PE.EmployeeID = E.ID
WHERE 
    E.Age > 50
GROUP BY 
    PE.EmployeeID, E.Name, E.Age
HAVING 
    COUNT(*) > 10

以下是执行计划链接:https://www.dropbox.com/s/rlp3bx10ty3c1mf/ximExPlan.sqlplan?dl=0

然而,执行它需要太多时间。它有什么问题?是否可以提高查询效率?

我有一个限制 - 我不能使用CTE。我相信它无论如何也不会带来性能。

3 个答案:

答案 0 :(得分:2)

在创建索引之前,我相信我们可以重新构建查询。

您的查询可以像这样重写

SELECT E.ID,
       E.NAME,
       E.Age,
       CS.ProductCount,
       CS.LastProductName
FROM   Employee E
       CROSS apply(SELECT TOP 1 P.NAME AS LastProductName,
                                ProductCount
                   FROM   (SELECT *,
                                  Count(1)OVER(partition BY EmployeeID) AS ProductCount -- to find product count for each employee
                           FROM   ProductEmployee PE
                           WHERE  PE.EmployeeID = E.Id) PE
                          JOIN Product P
                            ON PE.ProductID = P.ID
                   WHERE  ProductCount > 10 -- to filter the employees who is having more than 10 products
                   ORDER  BY date DESC) CS -- To find the latest sold product
WHERE  age > 50 

答案 1 :(得分:1)

这应该有效:

SELECT *
FROM Employee AS E
INNER JOIN (
    SELECT PE.EmployeeID
    FROM ProductEmployee AS PE
    GROUP BY PE.EmployeeID
    HAVING COUNT(*) > 10
    ) AS PE
    ON PE.EmployeeID = E.ID
CROSS APPLY (
    SELECT TOP (1) P.*
    FROM Product AS P
    INNER JOIN ProductEmployee AS PE2
        ON PE2.ProductID = P.ID
    WHERE PE2.EmployeeID = E.ID
    ORDER BY PE2.Date DESC
    ) AS P
WHERE E.Age > 50;

正确的索引应该加快查询速度 您按Age进行过滤,因此关注一个应该有所帮助:

CREATE INDEX ix_Person_Age_Name
    ON Person (Age, Name);

应首先计算查找超过10条记录的员工的子查询,CROSS APPLY应使用TOP运算符更有效地恢复数据,而不是将其与MAX值进行比较。

@Prdp回答很好,但我想我会放弃一个替代方案。有时窗口函数不能很好地工作,用ol'good子查询替换它们是值得的。

此外,请勿使用datetime,请使用datetime2。这是微软的建议: https://msdn.microsoft.com/en-us/library/ms187819.aspx

  

使用时间日期 datetime2 datetimeoffset 数据   新工作的类型。这些类型与SQL标准一致。他们是   更便携。 时间 datetime2 datetimeoffset 提供   更精确的秒数。 datetimeoffset 提供时区支持   用于全球部署的应用程序。

顺便说一下,这是一个提示。尝试在表格后命名您的代理主键,以便它们变得更有意义,并且连接感觉更自然。即:

  • 在员工表格中,将ID替换为EmployeeID
  • 在产品表格中,将ID替换为ProductID

我发现这是一个很好的做法。

答案 2 :(得分:0)

with usersOver50with10productsOrMore (employeeID, productID, date, id, name, age, products ) as  (
    select employeeID, productID, date, id, name, age, count(productID) from productEmployee 
    join employee on productEmployee.employeeID = employee.id  
    where age >= 50
    group by employeeID, productID, date, id, name, age
    having count(productID) >= 10 
    )
select sfq.name, sfq.age, pro.name, sfq.products, max(date) from usersOver50with10productsOrMore as sfq
join product pro on sfq.productID = pro.id
group by sfq.name, sfq.age, pro.name, sfq.products
; 

无需为整个表找到最后一个产品ID,只需填写10个或更多产品和50岁以上员工的最后一个产品。