Вопрос

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

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

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

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

-- 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 в отношении пользовательских функций.

Это было полезно?

Решение

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

Но позвольте мне начать с самого начала.Я почти уверен , что 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

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

Также, если вам действительно нужна ваша функция, почему бы не изменить ее реализацию таким образом, чтобы функция возвращала и ИДЕНТИФИКАТОР после первого успешного соединения?Это сэкономит вам уйму времени.

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

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

Другие советы

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

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

Одна вещь, которую следует иметь в виду, - это избегать "НЕ", если это вообще возможно."NOT" не является саргируемым, он не сможет в полной мере воспользоваться преимуществами индексации.На первый взгляд, я не вижу способа переписать его, чтобы избежать выражений NOT.Тьфу-тьфу, ИММ-МВ.:-)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top