2009-11-06 62 views
3

我目前正在研究一个特别复杂的用例。简化如下:)Sql子查询函数的性能

首先,客户端记录与服务集合具有多对一的关系,也就是说,单个客户端可能有多个与之关联的服务。

在我的触发器中,我正在编写一个Query,它根据特定条件返回客户端的ID。的标准如下,

  1. 如果至少一个服务是B型的,并且没有A型的服务存在,返回ID
  2. 如果至少一个服务是C型的,并没有类型的服务B或A存在,返回ID
  3. 如果至少一个服务的类型d,也没有C型或B或A的服务存在,返回ID

和我目前的做法是形成类似查询到下面的一个

SELECT c.ClientId 
FROM 
    Clients AS c 
    -- actually INNER JOIN is superfluous in this sample, but required for 
    -- other auxilliary criteria i have left out. illustrates relationship 
    -- between Clients and Services table 
    INNER JOIN Services AS s ON c.ClientId = s.ClientId 
WHERE 
-- has at least one service of type B, no A 
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) OR 

-- has at least one service of type C, no B, no A 
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) OR 

-- has at least one service of type D, no C, no B, no A 
(EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'D')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) AND 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A'))) 

其中[dbo].[Get_ServicesByClientIdAndType]是为指定的客户端ID和服务类型返回关联服务的函数。到

-- this query is actually significantly more complex than shown 
-- below, but this illustrates use of parameters client id and 
-- service type 
SELECT s.ServiceType 
FROM 
    Services AS s 
WHERE 
    s.ClientId = @clientId AND 
    s.ServiceType = @serviceType 

类似的假设这是表达这种用例优化手段,将功能[dbo].[Get_ServicesByClientIdAndType]子查询被缓存或不改变服务参数必须使用新的评估每个调用? [我正在调用这个东西像9次!运行Sql Server 2005]

我知道Sql Server 2005支持一些子查询优化,比如缓存结果,但我不确定在什么情况下或者如何形成我的子查询[或者函数]我充分利用了Sql Server的功能。


编辑:审查我的标准之上,不能让一个唠叨的感觉的东西走得掉。我在我的脑海一些逻辑发挥各地,以及与此[更为简单配方

SELECT c.ClientId 
FROM 
    Clients AS c 
    INNER JOIN Services AS s ON c.ClientId = s.ClientId 
WHERE 
    NOT EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')) AND 
    (EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'B')) OR 
    EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'C')) OR 
    EXISTS (SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'D'))) 

本质上来了,不存在的情况涉及B会导致排斥反应,同样,对于C和d,所以任何配置可以接受。我们只关心A不存在于任何选择中。 Arg!查理·布朗!


留下两个表达式进行审查,我还是很欣赏有关SQL Server的性能WRT用户自定义函数的响应。

回答

3

我写了你的问题,在此期间,你已经改变了你的要求,但你不应该有任何问题,我的解决方案转化为您的特定需求的答案..

但让我从开始开始时。 我很确定SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')没有被服务器缓存。这不是那么聪明;)因此它在主查询中被多次计算。

所以你的第一次优化应该朝那个方向发展。您应该减少调用Get_ServicesByClientIdAndType时的次数。你可以用很多方式做到这一点。但一般规则是,您应该为所有客户计算此功能的所有可能结果。这些结果应该放在一些临时表中,或者将它们放入由SQL Server本身制作的虚拟表中。

当您获得所有可能的结果时,只需将它们与您的客户表一起加入即可。但你只能加入他们ONCE

当然,许多事情和优化技巧取决于你的真实例子。在你给出的例子中,甚至不需要使用Get_ServicesByClientIdAndType。 为什么不简单地加入这两个表并对它们进行一些计算?

看看这个查询:

SELECT A.* FROM 
(
SELECT C.ClientID, 
    SUM(CASE(S.ServiceType) WHEN 'A' THEN 1 ELSE 0 END) AS ServiceA, 
    SUM(CASE(S.ServiceType) WHEN 'B' THEN 1 ELSE 0 END) AS ServiceB, 
    SUM(CASE(S.ServiceType) WHEN 'C' THEN 1 ELSE 0 END) AS ServiceC, 
    SUM(CASE(S.ServiceType) WHEN 'D' THEN 1 ELSE 0 END) AS ServiceD 
FROM Clients AS C 
INNER JOIN Services AS s ON c.ClientId = s.ClientId 
GROUP BY C.ClientID 
) A 
WHERE ((A.ServiceB > 0) AND (A.ServiceA = 0)) 
OR ((A.ServiceC > 0) AND (A.ServiceA = 0) AND (A.ServiceB = 0)) 
OR ((A.ServiceD > 0) AND (A.ServiceA = 0) AND (A.ServiceB = 0) AND (A.ServiceC = 0)) 

在内部解决问题,我们加入了表。因为我们不需要它,所以我们抛弃了这个功能。相反,我们计算每个客户端的不同服务的数量。接下来通过内部查询结果我们实现您的条件。我们只是简单地检查特定集合中给定服务的发生。

的结果是这样的:

ClientID ServiceA ServiceB ServiceC ServiceD 
-------- -------- -------- -------- -------- 
26915  0  4  2  2 
26917  0  0  1  1 
26921  0  3  2  3 
26927  0  4  2  4 

当然你也可以从服务栏脱衣最终结果。我已经包含了它们,因为我喜欢它;-)它允许检查查询是否正常工作。 您甚至可以编写一个查询,该查询不会计算给定客户端的给定服务类型的数量。它将工作得更快,并给你正确的结果。

此外,如果你真的需要你的函数,为什么不改变它的实现方式,该函数将返回和第一次succesfull连接后的ID?它会为你节省很多时间。

但是只有你自己知道的大局观,因此所有我写到这里可能是垃圾;-)

无论如何,我希望我帮你以某种方式。

+0

哇,非常酷。它是有道理的,基本上是在单个子查询中列出事件,然后从中进行选择。我只在一个更简单但等同的表达式中重申了我的条件 - 对您的解决方案的唯一影响是更简单的外部WHERE子句WHERE A.ServiceA = 0 AND(A.ServiceB> 0或A.ServiceC> 0或A.ServiceD> 0 )' – 2009-11-06 21:14:51

1

我想,sql服务器调用你的函数Get_ServicesByClientIdAndType每个组合的参数值,但每一个客户端表中的行。您有三种值的组合,因此对于客户端表中的100行,您可能会看到300个函数调用。

但要确信,请在sql server management studio中运行查询并切换“显示执行计划”选项。通过这种方式,您可以轻松地检测到查询的哪一部分消耗了大量资源,并优化该部分。

0

要记住的一件事就是尽可能避免“不”。 “不”是不可测的,它不能充分利用索引。乍一看,我没有看到重写它的方法来避免NOT表达式。 FWIW,YMMV。 :-)