2009-11-06 1 views
3

В настоящее время я работаю над особенно сложным прецедентом. Упрощение ниже:Выполнение подзапросов Sql functions

Во-первых, запись клиента имеет отношение «много-к-одному» с набором услуг, то есть один клиент может иметь несколько связанных с ним служб.

Внутри моего триггера я пишу запрос, который возвращает идентификатор клиента на основе определенных критериев. Эти критерии являются следующими,

  1. Если по крайней мере одна услуги типа В, и никаких услуг типа А не существуют, обратный идентификатор
  2. Если по крайней мере одна услуги типа С, и нет услуги типа в или а есть, возвращение идентификатор
  3. Если по крайней мере один сервис типа D, и никаких услуг типа с или в, или а не существуют, удостоверение личности возвращения

и мой нынешний подход, чтобы сформировать запрос аналогичного к которому ниже

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] - это функция, которая возвращает связанные службы для указанного идентификатора клиента и типа сервиса. Подобно

-- 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.


EDIT: рассмотрел мои критерии выше, и не мог отпустить нытье чувство чего-то был выключен. Я играл с какой-то логикой в ​​моей голове, и пришел с этим [гораздо проще] формулировками

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, так что любая конфигурация является приемлемым. мы только заботимся о том, чтобы А отсутствовал в любом выборе. Arg! Чарли Браун!


оставляя оба выражения для обзора, и я до сих пор очень ценю ответы, касающиеся определенных пользовательских функций производительности WRT SQL Server.

ответ

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 

Конечно, вы можете раздеться конечный результат из колонок обслуживания. Я включил их, потому что мне это нравится ;-) И это позволяет проверить, правильно ли работает запрос. Вы даже можете написать запрос, который не будет рассчитать номер данного типа услуги для данного клиента. Он будет работать еще быстрее и даст вам правильные результаты.

Также, если вам действительно нужна ваша функция, почему бы не изменить ее реализацию так, чтобы функция вернулась и идентификатор после первого succesfull join? Это сэкономит вам много времени.

Но только вы знаете, более широкую картину, так все, что я написал здесь, может быть мусор ;-)

Во всяком случае, я надеюсь, что я помог вам в некотором роде.

+0

Ничего себе, очень круто. Это имеет смысл, по существу, табулировать события в одном подзапросе, а затем выбирать из этого. Я только переформулировал свои критерии в более простом, но эквивалентном выражении - единственный эффект на ваше решение - это более простое внешнее предложение WHERE 'WHERE A.ServiceA = 0 AND (A.ServiceB> 0 ИЛИ A.ServiceC> 0 ИЛИ A.ServiceD> 0) ' –

1

Я бы предположил, что sql-сервер вызывает вашу функцию Get_ServicesByClientIdAndType один раз для каждой комбинации значений параметров, но для каждой строки в таблице «Клиенты». У вас есть три комбинации значений, поэтому для 100 строк в таблице Client вы можете увидеть 300 вызовов функции.

Но, чтобы быть уверенным, запустите запрос в студию управления сервером sql и включите опцию «показать план выполнения». Таким образом, вы можете легко определить, какая часть вашего запроса потребляет большинство ресурсов и концентрируется на оптимизации этой части.

0

Следует иметь в виду, что следует избегать «НЕ», если это вообще возможно. «НЕ» не поддается переходу, он не сможет воспользоваться всеми преимуществами индексации. На первый взгляд, я не вижу способа переписать его, чтобы избежать выражений NOT. FWIW, YMMV. :-)