Вопрос

У меня есть случай, когда мне нужно перевести (выполнить поиск) несколько значений из одной таблицы.Первым способом, которым я это написал, было использование подзапросов:

SELECT
    (SELECT id FROM user WHERE user_pk = created_by) AS creator,
    (SELECT id FROM user WHERE user_pk = updated_by) AS updater,
    (SELECT id FROM user WHERE user_pk = owned_by) AS owner,
    [name]
FROM asset

Поскольку я часто использую этот подзапрос (то есть у меня около 50 таблиц с этими полями), и мне, возможно, потребуется добавить еще немного кода к подзапросу (например, "И active = 1" ), я подумал, что помещу их в определяемую пользователем функцию UDF и используй это.Но производительность при использовании этого UDF была ужасающей.

CREATE FUNCTION dbo.get_user ( @user_pk INT )
RETURNS INT
AS BEGIN 
    RETURN ( SELECT id
             FROM   ice.dbo.[user]
             WHERE  user_pk = @user_pk )
END

SELECT dbo.get_user(created_by) as creator, [name]
FROM asset

Выполнение #1 занимает менее 1 секунды.Выполнение #2 занимает около 30 секунд...

Почему, или, что более важно, есть ли какой-либо способ, которым я могу кодировать в SQL server 2008, чтобы мне не приходилось использовать так много подзапросов?

Редактировать:

Просто еще немного объясню, когда это полезно.Этот простой запрос (то есть получение идентификатора пользователя) становится намного сложнее, когда я хочу получить текст для пользователя, поскольку мне нужно подключиться к profile, чтобы получить язык, к компании, чтобы узнать, следует ли извлекать язык оттуда, и к таблице перевода, чтобы получить переведенный текст.И для большинства из этих запросов производительность является второстепенной проблемой по сравнению с удобочитаемостью и ремонтопригодностью.

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

Решение

UDF - это черный ящик для оптимизатора запросов, поэтому он выполняется для каждой строки.Вы перемещаете курсор по строкам.Для каждой строки в активе найдите идентификатор три раза в другой таблице.Это происходит, когда вы используете скалярные или многопроцессорные UDF (встроенные UDF - это просто макросы, которые расширяются во внешний запрос).

Одна из многих статей , посвященных этой проблеме , такова "Скалярные функции, встраивание и производительность:Занимательный заголовок для скучного поста".

Подзапросы могут быть оптимизированы для корреляции и избежания построчных операций.

Чего ты действительно хочешь, так это этого:

SELECT
   uc.id AS creator,
   uu.id AS updater,
   uo.id AS owner,
   a.[name]
FROM
    asset a
    JOIN
    user uc ON uc.user_pk = a.created_by
    JOIN
    user uu ON uu.user_pk = a.updated_by
    JOIN
    user uo ON uo.user_pk = a.owned_by

Обновление за февраль 2019 года

SQL Server 2019 начинает устранять эту проблему.

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

Как предполагалось в других постерах, использование объединений, безусловно, обеспечит вам наилучшую общую производительность.

Однако, поскольку вы заявили, что вам не нужна головная боль от поддержания 50-иш аналогичных объединений или подзапросов, попробуйте использовать встроенную табличную функцию следующим образом:

CREATE FUNCTION dbo.get_user_inline (@user_pk INT)
RETURNS TABLE AS
RETURN
(
    SELECT TOP 1 id
    FROM ice.dbo.[user]
    WHERE user_pk = @user_pk
        -- AND active = 1
)

Тогда ваш первоначальный запрос стал бы чем-то вроде:

SELECT
    (SELECT TOP 1 id FROM dbo.get_user_inline(created_by)) AS creator,
    (SELECT TOP 1 id FROM dbo.get_user_inline(updated_by)) AS updater,
    (SELECT TOP 1 id FROM dbo.get_user_inline(owned_by)) AS owner,
    [name]
FROM asset

Ан встроенная табличная функция должна обладать лучшей производительностью, чем скалярная функция или функция с несколькими табличными значениями.

Производительность должна быть примерно эквивалентна вашему исходному запросу, но любые будущие изменения могут быть внесены в UDF, что сделает его намного более удобным в обслуживании.

Чтобы получить тот же результат (NULL, если пользователь удален или неактивен).

 select 
    u1.id as creator,
    u2.id as updater,
    u3.id as owner,
    [a.name]
 FROM asset a
        LEFT JOIN user u1 ON (u1.user_pk = a.created_by AND u1.active=1) 
        LEFT JOIN user u2 ON (u2.user_pk = a.created_by AND u2.active=1) 
        LEFT JOIN user u3 ON (u3.user_pk = a.created_by AND u3.active=1) 

Я что-то упускаю?Почему это не может сработать?Вы только выбираете идентификатор, который у вас уже есть в таблице:

select created_by as creator, updated_by as updater, 
owned_by as owner, [name]
from asset

Кстати, при проектировании вам действительно следует избегать ключевых слов, таких как name, как имена полей.

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