【问题标题】:SQL Server query slow - how do optimize it?SQL Server 查询慢——如何优化呢?
【发布时间】:2019-06-05 04:12:31
【问题描述】:

我需要一些帮助以更快地优化此查询。

此查询生成一个视图,后者将显示在网站的表格中。 这个查询很慢,我正在努力让它更快。

到目前为止,我唯一尝试过的就是减少为表检索的列数。

这是查询:

SELECT        TOP (100) PERCENT Id, MAX(BusinessTitle) AS BusinessTitle, MAX(ClientName) AS ClientName, MAX(ClientType) AS Type, MAX(CreatedWhen) AS CreatedWhen, MAX(CASE WHEN C.[CreatedBy] IS NULL 
                     THEN 'Client' ELSE 'Admin' END) AS CreatedBy, CAST(MAX(CASE WHEN C.IsDisabled = 1 THEN 1 ELSE 0 END) AS BIT) AS IsDisabled, MAX(ReferenceSource) AS ReferenceSource, MAX(OtherReferenceSource) 
                     AS OtherReferenceSource, MAX(Address) AS Address,
                         (SELECT        MAX(T.FirstName + ' ' + T.LastName) AS Expr1
                           FROM            dbo.ApplicationUsers AS A INNER JOIN
                                                     dbo.Therapists AS T ON A.UserName = MAX(C.ClientName) AND A.Id = T.ApplicationUserId) AS ClientAdmin,
                         (SELECT        MAX(A.Email) AS Expr1
                           FROM            dbo.ApplicationUsers AS A INNER JOIN
                                                     dbo.Therapists AS T ON A.UserName = MAX(C.ClientName) AND A.Id = T.ApplicationUserId) AS Email,
                         (SELECT        MAX(Name) AS Expr1
                           FROM            dbo.Cities AS CY
                           WHERE        (Id = MAX(C.CityId))) AS City,
                         (SELECT        COUNT(*) AS Expr1
                           FROM            dbo.Patients AS P
                           WHERE        (ClientId = C.Id)) AS TotalPatientCount,
                         (SELECT        COUNT(*) AS Expr1
                           FROM            dbo.Patients AS P
                           WHERE        (ClientId = C.Id) AND (IsDeleted = 0) AND (IsDisabled = 0)) AS ActivePatientCount,
                         (SELECT        MAX(CreatedWhen) AS Expr1
                           FROM            dbo.Patients AS P
                           WHERE        (ClientId = C.Id)) AS LastPatientAddition,
                         (SELECT        COUNT(*) AS Expr1
                           FROM            dbo.Treatments AS T
                           WHERE        (ClientId = C.Id)) AS TotalTreatmentCount,
                         (SELECT        MAX(CreatedWhen) AS Expr1
                           FROM            dbo.Treatments AS T
                           WHERE        (ClientId = C.Id)) AS LastTreatmentAddition,
                         (SELECT        COUNT(*) AS Expr1
                           FROM            dbo.Therapists AS T
                           WHERE        (ClientId = C.Id)) AS TotalTherapistCount,
                         (SELECT        COUNT(*) AS Expr1
                           FROM            dbo.Therapists AS T INNER JOIN
                                                     dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id
                           WHERE        (A.IsDeleted = 0) AND (A.IsDisabled = 0)) AS ActiveTherapistCount,
                         (SELECT        MAX(A.CreatedWhen) AS Expr1
                           FROM            dbo.Therapists AS T INNER JOIN
                                                     dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id) AS LastTherapistAddition,
                         (SELECT        MAX(A.LastLoginDate) AS Expr1
                           FROM            dbo.Therapists AS T INNER JOIN
                                                     dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id
                           WHERE        (A.LastLoginDate IS NOT NULL)) AS TherapistLastLoginDate, CAST((CASE WHEN
                         ((SELECT        COUNT(S.[Id])
                             FROM            [dbo].[ClientSubscriptions] AS S
                             WHERE        ((S.[ClientId] = C.[Id]) AND (S.[IsDeleted] = 0) AND ((S.[SubscriptionEnd] IS NULL) OR
                                                      (S.[SubscriptionEnd] > GETDATE())))) > 0) THEN 1 ELSE 0 END) AS BIT) AS HasActiveSubscription,
                         (SELECT        MAX(SubscriptionEnd) AS Expr1
                           FROM            dbo.ClientSubscriptions AS S
                           WHERE        (ClientId = C.Id) AND (IsDeleted = 0) AND (SubscriptionEnd IS NULL OR
                                                     SubscriptionEnd > GETDATE())) AS LastValidSubscriptionEnd, CAST((CASE WHEN
                         ((SELECT        COUNT(S.[Id])
                             FROM            [dbo].[ClientSubscriptions] AS S
                             WHERE        ((S.[ClientId] = C.[Id]) AND (S.[IsDeleted] = 0) AND ((S.[SubscriptionEnd] IS NULL) OR
                                                      (S.[SubscriptionEnd] > GETDATE())) AND (S.[Id] <>
                                                          (SELECT        MIN(S2.[Id])
                                                            FROM            [dbo].[ClientSubscriptions] AS S2
                                                            WHERE        ((S2.[ClientId] = C.[Id]) AND (S2.[IsDeleted] = 0)))))) > 0) THEN 1 ELSE 0 END) AS BIT) AS IsPayingCustomer, COALESCE
                         ((SELECT        MAX(MonthlyPrice) AS Expr1
                             FROM            dbo.ClientSubscriptions AS S
                             WHERE        (ClientId = C.Id) AND (IsDeleted = 0) AND (SubscriptionEnd IS NULL OR
                                                      SubscriptionEnd > GETDATE()) AND (MonthlyPrice > 0)), 0.00) AS ActiveSubscriptionMonthlyPrice, MAX(ClientStatus) AS Status, MAX(Phone1) AS Phone, MAX(Phone2) AS Phone2,
                         (SELECT        Code
                           FROM            dbo.DiscountCoupons AS DC
                           WHERE        (Code =
                                                         (SELECT        TOP (1) DiscountCouponCode
                                                           FROM            dbo.ClientPayments AS CP
                                                           WHERE        (ClientId = C.Id)
                                                           ORDER BY Id))) AS DiscountCouponCode,
                         (SELECT        IssuedTo
                           FROM            dbo.DiscountCoupons AS DC
                           WHERE        (Code =
                                                         (SELECT        TOP (1) DiscountCouponCode
                                                           FROM            dbo.ClientPayments AS CP
                                                           WHERE        (ClientId = C.Id)
                                                           ORDER BY Id))) AS DiscountCouponIssuedTo,
                         (SELECT        ClientDiscount
                           FROM            dbo.DiscountCoupons AS DC
                           WHERE        (Code =
                                                         (SELECT        TOP (1) DiscountCouponCode
                                                           FROM            dbo.ClientPayments AS CP
                                                           WHERE        (ClientId = C.Id)
                                                           ORDER BY Id))) AS DiscountCouponClientDiscount, COALESCE
                         ((SELECT        COUNT(Id) AS Expr1
                             FROM            dbo.ClientFiles AS F
                             WHERE        (ClientId = C.Id)), 0) AS TotalFilesCount, COALESCE
                         ((SELECT        SUM(FileSize) AS Expr1
                             FROM            dbo.ClientFiles AS F
                             WHERE        (ClientId = C.Id)), 0) / 1048576.0 AS TotalFilesSize, CAST(MAX(CASE WHEN C.CrmEnded = 1 THEN 1 ELSE 0 END) AS BIT) AS CrmEnded, MAX(CrmStatus) AS CrmStatus, MAX(CrmUnuseReason) 
                     AS CrmUnuseReason,
                         (SELECT        COUNT(1) AS Expr1
                           FROM            dbo.Tipulog_Crm_Calls_new AS CC
                           WHERE        (Cust_id = C.Id)) AS CrmCallCount
FROM            dbo.Clients AS C
WHERE        (IsDeleted = 0)
GROUP BY Id

【问题讨论】:

  • 您希望我们如何在没有任何解释的情况下阅读您的查询并理解您想要做什么?请格式化您的查询并提供一些示例数据和预期结果的解释
  • 我从未见过需要这么多嵌套子查询的查询。首先,有许多引用相同的表 - 添加一个子查询,在一个地方完成所有这些并加入它。如果您想要获得不错的性能,您必须解决其中的一些问题。
  • 欢迎来到stackoverflow。请相应地阅读T-SQL tag infoedit 您的问题。

标签: sql sql-server sql-server-2017


【解决方案1】:

我将添加第二个答案,即完整的sql。这当然没有经过测试,因为我们无法访问您的数据,但我认为您应该能够自己调试它。这段代码中有很多指针应该告诉你如何去。

基本的事情是取出所有相关的查询并将它们作为子查询。这样做的唯一原因是您使用的所有 Max/Min - 我会根据您的数据将它们视为不必要的,然后您应该将它们取出并直接加入表。所有子查询都是左连接 - 如果可以的话,再次将它们设为正常连接,具体取决于您的数据。

还按 Id 取出了外部组,因为 99% 确定这不是必需的,前 100% 位也是如此。

SELECT  BusinessTitle, ClientName, ClientType AS Type, CreatedWhen, 
        CASE WHEN C.[CreatedBy] IS NULL THEN 'Client' ELSE 'Admin' END) AS CreatedBy, 
        CAST(CASE WHEN C.IsDisabled = 1 THEN 1 ELSE 0 END AS BIT) AS IsDisabled, 
        ReferenceSource, OtherReferenceSource, Address,
        ApplicationByName.FullName AS ClientAdmin,
        ApplicationByName.Email AS Email,
        Cities.Name AS City,
        Patients.TotalPatientCount,
        Patients.ActivePatientCount,
        Patients.LastPatientAddition,
        Treatments.TotalTreatmentCount,
        Treatments.LastTreatmentAddition,
        Therapists.TotalTherapistCount,
        Therapists.ActiveTherapistCount,
        Therapists.LastTherapistAddition,
        Therapists.TherapistLastLoginDate
        CAST(CASE WHEN Subscriptions.SubscriptionCount>0 then 1 else 0 end as BIT) as HasActiveSubscription,
        Subscriptions.LastValidSubscriptionEnd
        CAST(Subscriptions.IsPayingCustomer AS BIT) AS IsPayingCustomer, 
        COALESCE(ActiveSubscriptionMonthlyPrice,0) as ActiveSubscriptionMonthlyPrice
        ClientStatus AS Status, Phone1 AS Phone, Phone2 AS Phone2,
        ClientPayments.DiscountCouponCode,
        DiscountCoupons.IssuedTo AS DiscountCouponIssuedTo,
        DiscountCoupons.ClientDiscount AS DiscountCouponClientDiscount, 
        COALESCE(ClientFiles.TotalFilesCount,0) AS TotalFilesCount, 
        COALESCE(ClientFiles.TotalFilesSize,0) AS TotalFilesSize, 
        CAST((CASE WHEN C.CrmEnded = 1 THEN 1 ELSE 0 END) AS BIT) AS CrmEnded, 
        CrmStatus, CrmUnuseReason,
        Crm_Calls.CrmCallCount
FROM dbo.Clients AS C
left join (
    select A.UserName,
        max(T.FirstName + ' ' + T.LastName) as FullName,
        max(A.Email) as Email
    from dbo.ApplicationUsers A
    join dbo.Therapists T on T.ApplicationUserId=A.Id
    group by A.Username
    ) ApplicationByName on ApplicationByName.UserName=C.ClientName
join dbo.Cities on Cities.ID=c.CityID
left join (
    SELECT ClientId,
        COUNT(*) AS TotalPatientCount,
        sum(case when IsDeleted = 0 AND IsDisabled = 0 then 1 else 0 end) AS ActivePatientCount,
        MAX(CreatedWhen) AS LastPatientAddition
    FROM  dbo.Patients
    GROUP BY ClientId
    ) Patients on Patients.ClientId = C.Id
left join (
    SELECT ClientId,
        COUNT(*) AS TotalTreatmentCount,
        MAX(CreatedWhen) AS LastTreatmentAddition
    FROM dbo.Treatments
    GROUP BY ClientId
    ) Treatments on Treatments.ClientID = C.Id
left join (
    select T.ClientId, 
        count(distinct T.Id) as TotalTherapistCount,
        sum(case when A.IsDeleted = 0 AND A.IsDisabled = 0 then 1 else 0 end) as ActiveTherapistCount,
        max(A.CreatedWhen) as LastTherapistAddition,
        max(A.LastLoginDate) as TherapistLastLoginDate
    from Therapists T
    left join dbo.ApplicationUsers A on A.Id=T.ApplicationUserId 
    group by T.ClientId
    ) Therapists on Therapists.ClientID = C.Id
left join (
    SELECT S.ClientId,
        count(*) as SubscriptionCount,
        MAX(SubscriptionEnd) as LastValidSubscriptionEnd,
        MAX(case when MinSub.Id!=S.ID then 1 else 0 end as IsPayingCustomer,
        max(case when MonthlyPrice>0 then 0 end) as ActiveSubscriptionMonthlyPrice
    FROM dbo.ClientSubscriptions S
    join (
        select ClientId, min(Id) as Id 
        from dbo.ClientSubscriptions 
        where IsDeleted=0 
        group by ClientId
        ) MinSub on MinSub.ClientId=ClientSubscriptions.ClientId
    where IsDeleted=0 and (SubscriptionEnd is null or SubscriptionEnd>getdate())
    group by ClientId
    ) Subscriptions on Subscriptions.ClientId=C.Id
left join (
    select ClientId, 
        DiscountCouponCode,
        row_number() over(partition by ClientId, order by Id) rn
    from  dbo.ClientPayments
    ) ClientPayments on ClientPayments.ClientId=C.ID and rn=1
left join dbo.DiscountCoupons on DiscountCoupons.Code=ClientPayments.DiscountCouponCode
left join (
    select ClientId,
        count(*) as TotalFilesCount,
        sum(FileSize)/1048576.0 as TotalFilesSize
    from dbo.ClientFiles
    group by ClientId
    ) ClientFiles on ClientFiles.ClientId=Client.Id
left join (
    SELECT Cust_id, COUNT(1) AS CrmCallCount
    FROM dbo.Tipulog_Crm_Calls_new 
    group by Cust_id
    ) Crm_Calls on Crm_Calls.Cust_id=C.Id
WHERE C.IsDeleted = 0

【讨论】:

  • 感谢您的帮助。顺便说一句,这不是我的 sql 查询,我被要求改进它,我会尝试你的更改,非常感谢。
  • 嗨,TomC,您的查询很棒,它使查询时间缩短了 4 秒,非常感谢!
  • 太棒了@Ben.S 如果您对它感到满意,请记住接受答案。
  • 嗨@TomC 你认为还有另一种优化这个查询的方法吗?它更快,但可以更多吗?
  • 我假设所有相关索引都已到位?此外,其他一些线程发现用 CROSS APPLY 替换左连接可能会更快,因此您可以尝试一下。没有你的数据很难说。
【解决方案2】:

这是一个非常部分的答案,但您询问了如何在多个子查询中引用一个表一次而不是多次。

这是一个示例,说明如何将所有这些子查询替换为患者和治疗表以及城市表。您确实需要了解联接。

FROM dbo.Clients AS C
join dbo.Cities on Cities.ID=c.CityID
left join (
    SELECT ClientId,
        COUNT(*) AS TotalPatientCount,
        sum(case when IsDeleted = 0 AND IsDisabled = 0 then 1 else 0 end) AS ActivePatientCount,
        MAX(CreatedWhen) AS LastPatientAddition
    FROM  dbo.Patients
    GROUP BY ClientId
    ) Patients on Patients.ClientId = C.Id
left join (
    SELECT ClientId,
        COUNT(*) AS TotalTreatmentCount,
        MAX(CreatedWhen) AS LastTreatmentAddition
    FROM dbo.Treatments
    GROUP BY ClientId
    ) Treatments on Treatments.ClientID = C.Id

然后,您的列列表将替换到 Patients 和 City 的子查询,如下所示:

Cities.Name AS City,
Patients.TotalPatientCount,
Patients.ActivePatientCount,
Patients.LastPatientAddition,
Treatments.TotalTreatmentCount,
Treatments.LastTreatmentAddition,

这至少应该给你一个想法。

【讨论】:

  • 谢谢TomC 我会尽量按照你的例子来减少这些子查询
  • 我很喜欢你对 TomC 所做的更改,但我得到的结果却更少。我删除了所有使用城市和患者表的子查询,还添加了连接和列列表,但得到的结果较少。你能举例说明如何组合子查询吗?我的意思是第一个子查询和第二个子查询。我正在使用具有弱表和参考调整的数据库。
  • @Ben.S 我已经添加了第二个子查询,并且也将连接更改为左连接,因为我猜有些客户可能没有患者,这可能就是您的行数较少的原因。即使您对某些子查询执行此操作,它也应该会提高您的性能。当然,还要确保您在患者、治疗等表中的 ClientID 上有索引。
  • 顺便说一句 - 如果您喜欢这种语法,您也可以将我的连接查询作为 CTE 执行。它应该是相同的结果,但您可能会发现它更容易阅读。
  • 非常感谢。你对那个左连接是正确的!结果现在与应有的相同。我阅读了有关 CTE 的信息,我也会尝试使用它们。如果您能给 CTE 举个例子,我将不胜感激。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 2016-12-31
相关资源
最近更新 更多