13

我有一个案例,我需要从同一个表中翻译(查找)几个值。我写它的第一种方式是使用子查询:

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 个包含这些字段的表),我可能需要向子查询添加更多代码(例如, "AND active = 1" )我想我' d 将它们放入用户定义的函数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 中编码,这样我就不必使用这么多子查询了?

编辑:

只是对何时有用的更多解释。当我想为用户获取文本时,这个简单的查询(即获取用户 ID)变得更加复杂,因为我必须加入个人资料才能获取语言,并与公司一起查看是否应该获取语言'而是从那里编辑,并使用翻译表来获取翻译后的文本。对于这些查询中的大多数,性能是可读性和可维护性的次要问题。

4

4 回答 4

34

UDF 对查询优化器来说是一个黑盒,因此它会针对每一行执行。您正在执行逐行游标。对于资产中的每一行,在另一个表中查找 id 三次。当您使用标量或多语句 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 年 2 月更新

SQL Server 2019 开始修复这个问题。

于 2009-02-04T10:09:14.653 回答
13

正如其他海报所建议的那样,使用连接肯定会给你最好的整体性能。

但是,由于您已声明您不想为维护 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 中进行任何未来的更改,使其更易于维护。

于 2009-02-04T11:54:54.177 回答
2

获得相同的结果(如果用户被删除或不活动,则为 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) 
于 2009-02-04T10:40:10.723 回答
0

我错过了什么吗?为什么这行不通?您只选择表中已有的 id:

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

顺便说一句,在设计时,您确实应该避免使用关键字,例如name,作为字段名称。

于 2009-02-04T14:53:11.353 回答