如何防止在Select语句中调用两次函数

时间:2015-02-25 00:12:08

标签: sql-server tsql

假设我有一个函数返回一个数字dbo.somefunction(@id INT)。然后我有另一个函数返回一点。 dbo.returnsbit(@id INT)

这就是我的观点

SELECT dbo.somefunction(id) AS returnIntValue
FROM sometable

最终这就是我想做的事情

SELECT dbo.somefunction(id) AS returnIntValue,, dbo.returnsbit(returnIntValue) As BitValue
FROM sometable

问题是,第二个函数(dbo.returnsbit)使用第一个函数(dbo.somefunction)返回的值。当然我可以做dbo.returnsbit(dbo.somefunction(id)),但这意味着第一个函数被调用两次导致开销增加。

2 个答案:

答案 0 :(得分:3)

通常,如果在查询中使用用户定义的函数来处理许多行,则查询的性能会显着下降,因为服务器必须为每一行调用函数。从这个意义上说,优化查询并且每行调用两个函数而不是三个函数不会改变整体性能,在任何情况下它都会很差。

如果你有一个非常复杂的函数,函数的每次调用需要很长时间,并且你希望将它调用为有限数量的行,那么优化调用的总数是有意义的。

无论如何,我发现这个问题很有趣,可以检查我的猜测并写下我的发现。


如果您使用的是SQL Server 2005或更高版本,则可以使用CROSS APPLY

SELECT
    CA.returnIntValue
    ,dbo.returnsbit(CA.returnIntValue) As BitValue
FROM
    sometable
    CROSS APPLY
    (
        SELECT dbo.somefunction(id) AS returnIntValue
    ) AS CA

我已经检查过SQL Server 2008,这个变体确实每行只调用somefunction一次。

有时候,从性能的角度来看,这一点很重要。如果带有副作用的函数被调用的次数多于所需的次数,则可能会得到不同的结果。见下面的例子。

以下是确认CROSS APPLY每行只调用一次somefunction的方法。

创建一个包含少量行的表

CREATE TABLE [dbo].[Numbers]([Number] [int] NOT NULL);

INSERT INTO [dbo].[Numbers] ([Number])
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);

创建第一个功能。它将返回一个随机数。每次调用时都会有不同的数字。它不会使用参数,但对于此示例无关紧要。

CREATE VIEW [dbo].[ViewRnd]
AS
SELECT CAST(CRYPT_GEN_RANDOM(4) AS int) AS rnd

CREATE FUNCTION [dbo].[somefunction] 
(
    @id int
)
RETURNS int
AS
BEGIN
    DECLARE @Result int;
    SELECT @Result = rnd FROM dbo.ViewRnd;
    RETURN @Result;
END

创建第二个功能。对于此示例,它将只返回给定的参数。

CREATE FUNCTION [dbo].[somefunction2]
(
    @id int
)
RETURNS int
AS
BEGIN
    DECLARE @Result int;
    SELECT @Result = @id;
    RETURN @Result;
END

每行两次调用的第一个查询:

SELECT
    dbo.somefunction(dbo.Numbers.Number) AS f1
    , dbo.somefunction2(dbo.somefunction(dbo.Numbers.Number)) AS f2
FROM dbo.Numbers
;

结果集:

f1              f2
-1111498263    -1481060640
 1678801669      230929974
 1377897182    -1527788053
 1786076194     -301754441
  734901522     1385475384
 -636644847    -1076939672
 1551114591     -385251162
   32984627    -1214863465
 2075259001    -1450159610
-2063202107    -1023434184

您可以看到每行中的值f1f2不同,这意味着每行生成两次随机数,即函数somefunction每次调用两次行。

每行一次调用的第二个查询:

SELECT
    CA.f1
    , dbo.somefunction2(CA.f1) AS f2
FROM
    dbo.Numbers
    CROSS APPLY
    (
        SELECT dbo.somefunction(dbo.Numbers.Number) AS f1
    ) CA
;

结果集:

         f1             f2
 -963307489     -963307489
  450369380      450369380
 1193334688     1193334688
 1480723291     1480723291
-1666937401    -1666937401
 1001969991     1001969991
-1142557574    -1142557574
-1891218324    -1891218324
 -102288163     -102288163
 1575326336     1575326336

您可以看到每行中的值f1f2相同,这意味着每行只调用一次函数somefunction

答案 1 :(得分:0)

你可以这样做:

SELECT returnIntValue, dbo.returnsbit(returnIntValue) As BitValue
FROM 
    (SELECT dbo.somefunction(id) AS returnIntValue
    FROM sometable) t

我猜dbo.somefunction可能仍然会在每行调用两次,具体取决于优化程序选择的查询计划,正如Damien_The_Unbeliever指出的那样。