3

我目前正在研究一个特别复杂的用例。简化如下:)

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

在我的触发器中,我正在编写一个查询,该查询根据某些条件返回客户的 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')))

where[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 在任何选择中都不存在。精氨酸!查理·布朗!


将这两个表达式都留待审查,我仍然非常感谢有关 Sql Server 性能 wrt 用户定义函数的响应。

4

3 回答 3

3

我正在为您的问题写一个答案,同时您已经更改了您的要求,但您应该没有任何问题将我的解决方案转换为您的特定需求..

但让我从头说起。我很确定SELECT * FROM Get_ServicesByClientIdAndType (c.ClientId, 'A')服务器无论如何都不会缓存它。它不是那么聪明;)所以它在你的主查询中被计算了多次。

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

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

当然很多事情和优化技巧取决于你的真实例子。在您给出的示例中,甚至不需要使用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

当然,您可以从服务列中删除最终结果。我将它们包括在内是因为我喜欢这种方式;-) 它允许检查查询是否正常工作。您甚至可以编写一个查询,它不会计算给定客户端的给定服务类型的数量。它将更快地工作并为您提供正确的结果。

此外,如果你真的需要你的函数,为什么不改变它的实现,让函数在第一次成功加入后返回和 ID?它将为您节省大量时间。

但只有你知道大局,所以我在这里写的可能都是垃圾;-)

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

于 2009-11-06T21:00:43.493 回答
1

我猜想sql server会为每个参数值组合调用一次你的函数Get_ServicesByClientIdAndType,但对于Clients表中的每一行都会调用一次。您有三种值组合,因此对于 Client 表中的 100 行,您可能会看到 300 次函数调用。

但是为了有信心,在 sql server management studio 中运行查询并打开选项“显示执行计划”。通过这种方式,您可以轻松检测查询的哪个部分消耗最多的资源并专注于优化该部分。

于 2009-11-06T15:10:22.407 回答
0

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

于 2009-11-06T15:51:22.470 回答