1

次のように機能する関数のロジックを複製しようとしています。

given input params @a,@b,@c:

look up return value @v in table where column a=@a and b=@b and c=@c
if found, return @v
if not found, perform complex calculations to derive @v
insert into lookup table (a, b, c, v) values (@a, @b, @c, @v)
return @v

テーブル内の値の検索は、複雑な計算よりもはるかに高速になるという考え方です。値のセットに対して複雑な計算を行う必要がある場合は、テーブルに行を追加して、次回@vの値を検索する方が速くなるようにします。

もちろん、副作用の理由から、UDFでINSERTステートメントを実行することはできません。

UDFでINSERTを実行できるようにするハックを求めているのではなく、同じタイプのロジックを実行するようにコードを再設計する方法についてのアイデアを求めていますか?目標はUDFを使用することです。これは、すべてがテーブルに存在する何千もの入力値セットの@v値を計算する必要があるためです。

4

3 に答える 3

0

値を計算してキャッシュを管理できる単一の関数がカードに含まれていないように見えるため、既存のサンプル データ セットに値を提供する合理的に効率的な手段の例を次に示します。UDF はキャッシュされた値または計算された値を提供しますが、キャッシュの更新は別のままです。

set nocount on;

-- Create some sample data and a partial cache of calculated values.
create table Samples ( Id Int Identity, A Int, B Int, V Int Null );
insert into Samples ( A, B ) values
  ( 0, 0 ), ( 0, 1 ), ( 1, 0 ), ( 1, 1 ), ( 2, 0 ), ( 0, 2 ), ( 0, 1 ), ( 0, 1 );

create table Cache ( Id Int Identity, A Int, B Int, V Int );
insert into Cache ( A, B, V ) values
  ( 1, 1, 1 ), ( 0, 2, 4 );
go

-- Create the function to perform the expensive calculation.
create function dbo.ExpensiveCalculation( @A Int, @B Int )
  returns Int
  as
  begin
  return @A * @A + @B * @B;
  end;
go

-- And another function that can use cached values.
create function dbo.ExpensiveCalculationWithCaching( @A Int, @B Int )
  returns Int
  as
  begin
  declare @Result as Int
  -- Try to get a cached value.
  select @Result = V
    from Cache
    where A = @A and B = @B;
  -- If we didn't find a cached value then calculate one.
  if @@RowCount = 0
    select @Result = dbo.ExpensiveCalculation( @A, @B );
  return @Result;
  end;
go

-- Apply any previously cached values to the samples.
update S
  set S.V = C.V
  from Samples as S inner join
    Cache as C on C.A = S.A and C.B = S.B;
print Cast( @@RowCount as VarChar(6) ) + ' samples satisfied from initial cache.'

declare @BatchSize as Int = 3; -- Number of rows to process with the function in each iteration.
declare @CacheIds as Table ( Id Int );

-- Update the samples one batch at a time.
while exists ( select 42 from Samples where V is NULL )
  begin

  -- Clear the intermediate data, if any.
  delete from @CacheIds;

  -- Find a batch of unknown values with distinct input values and apply the function.
  --   Add the results to the cache and note the id's of the new rows.
  insert into Cache
    output inserted.Id into @CacheIds
    select top (@BatchSize) A, B, dbo.ExpensiveCalculation( A, B )
      from Samples
      where V is NULL
      group by A, B;
  print Cast( @@RowCount as VarChar(6) ) + ' cache entries added.'

  -- Update any samples that benefit from the newly cached values.
  update S
    set S.V = C.V
    from Samples as S inner join
      Cache as C on C.A = S.A and C.B = S.B inner join
      @CacheIds as CI on CI.Id = C.Id
    where S.V is NULL;
  print Cast( @@RowCount as VarChar(6) ) + ' samples satisfied from cache update.'
  end

-- Display the results.
select Id, A, B, V
  from Samples

select dbo.ExpensiveCalculationWithCaching( 1, 1 ) as Cached,
  dbo.ExpensiveCalculationWithCaching( 4, 4 ) as Calculated

-- Houseclean.
drop function dbo.ExpensiveCalculationWithCaching;
drop function dbo.ExpensiveCalculation;
drop table Samples;
drop table Cache;

注:Samplesこのコードの実行中にテーブルに行が追加された場合、行は処理されない可能性があります。

于 2013-01-13T03:59:34.453 に答える
0

これは望ましい解決策ではありませんが、これまでのところ他の解決策を探しています... [LookUp] で INSERT TRIGGER を作成し、必要なすべての計算を行うことができます。最終クエリの準備として、UDF の代わりにルックアップを使用すると、必要なすべての値を強制的にルックアップすることができます。

Insert into [LookUp] (a,b,c)
Select ds.a,ds.b,ds.c from
(
Select Distinct a,b,c from [Source]
) ds
Left Join [LookUp] l on l.a=ds.a and l.b=ds.b and l.c=ds.c
Where l.a IS NULL
于 2013-01-12T07:11:25.597 に答える
0

編集:これはおそらく以下のものよりも優れたアプローチです。繰り返しますが、これは完全にテストされていません。これは、アイデアの出発点としてのみ意図されています。

CREATE FUNCTION dbo.MyFN_MKII_1
(
    @a INT,
    @b INT,
    @C INT
)
RETURNS TABLE 
WITH SCHEMABINDING 
AS
    RETURN  SELECT  v 
            FROM MyTable
            WHERE a = @a
            AND b = @b
            AND c = @c
;
GO

CREATE FUNCTION dbo.MyFN_MKII_2
(
    @a INT,
    @b INT,
    @C INT
)
RETURNS INT
WITH SCHEMABINDING 
AS
-- place the body of the function here
BEGIN
     RETURN POWER(@a,@b)*@c
END
GO

---------------------
--  Usage
---------------------

INSERT INTO LookupTable
SELECT   ST.ColA
        ,ST.ColB
        ,ST.ColC
        ,ISNULL(V, dbo.MyFN_MKII_2(ST.COlA, ST.ColB, ST.ColC))
FROM SomeTable  ST
CROSS APPLY dbo.MyFN_MKII_1(ColA, ColB, ColC)

これは完全にテストされていません。これは、アイデアの出発点としてのみ意図されています。コンセプトの最適化を提供できる非常に頭の良い人が周りにいます。

CREATE FUNCTION dbo.MyFN
(
    @a INT,
    @b INT,
    @C INT
)
RETURNS @Result TABLE 
(
    a INT,
    b INT,
    c INT,
    v INT
)
AS
BEGIN

    INSERT INTO @Result (a, b, c, v)
    SELECT @a, @b, @c, v
    FROM MyTable
    WHERE a = @a
    AND b = @b
    AND c = @c

    IF @@ROWCOUNT = 0
    BEGIN
        INSERT INTO @Result (a, b, c, v)
        SELECT @a, @b, @c, POWER(@a,@b) * @c
    END

   RETURN
END
GO


---------------------
--  Usage
---------------------

INSERT INTO LookupTable
SELECT *
FROM SomeTable
CROSS APPLY dbo.MyFN(ColA, ColB, ColC)

この関数は比較的遅くなると思います。

于 2013-01-11T21:18:56.320 に答える