0

かなり複雑なクエリがありますが、インデックスを適用したところ、1秒未満で非常にスムーズに実行されるようになりました。クエリの構造は次のようになります(後で証明するため、完全なクエリを投稿する必要はありません。障害はクエリ自体にありません)。

DECLARE @period varchar(6);
SET @period = '201302';

DECLARE @day datetime;
SET @day = dba.fnu_firstdate(@period);//returns 2013-02-01

SELECT
    user_id,
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 0, @day)) Day01,
    ...    
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 30, @day)) Day31
FROM
    table

そうですね、このクエリを実行すると、完了するまでに約1秒かかります。これは、私にとってはまったく問題ありません。ただし、ご覧のとおり、パラメータを指定する必要があります。したがって、それをテーブル値関数に変更して、そこから簡単に選択クエリを実行できるようにしました。

CREATE FUNCTION fnu_data(@period varchar(6))
RETURNS @results TABLE
(
    id int,
    Day01 varchar(10) null,
    ...
    Day31 varchar(10) null
)
AS
BEGIN
    DECLARE @day datetime;
    SET @day = dba.fnu_firstdate(@period);

    INSERT INTO @results 
    (
        id,     
        Day01,
        ...
        Day31
    )
    SELECT

SELECT
    user_id,
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 0, @day)) Day01,
    ...    
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 30, @day)) Day31
FROM
    table

RETURN

今私がするとき

SELECT * FROM dba.fnu_data('201302')

6秒かかりますが、長すぎます。同僚のアドバイスを受けて、idにプライマリインデックスを追加し、すべての副選択を結合として置き換えようとしましたが、クエリの実行時間が8秒に延長されました。(PSクエリは最大3200行を返します)。

私の意見では、犯人は挿入ですが、どうすればそれを取り除くことができるのかわかりません。

クエリを改善するにはどうすればよいですか?

4

2 に答える 2

1

SELECTスタンドアロンと関数の一部としてのパフォーマンスの違いの原因はわかりませんがINSERT ... SELECT、SELECTは私には明らかに最適ではないように見えるため、SELECTステートメントを書き直すことをお勧めします。

SQLServer2005以降にネイティブ構文があるピボットを実行しているようです。次のクエリについて考えてみます。

WITH data AS (
  SELECT
    user_id,
    DAY([when]) AS day,
    c1
  FROM [table] t
  CROSS APPLY (
    SELECT CAST(@period + '01' AS date)  -- this is supposed to be a replacement
                                         -- for dba.fnu_firstdate(), but you
                                         -- could use your function here instead
  ) x (startdate)
  WHERE t.day >= x.startdate
    AND t.day <  DATEADD(MONTH, 1, startdate)
)
INSERT INTO @results
(
  id,
  Day01,
  ...
  Day31
)
SELECT
  id,     
  [1],
  ...
  [31]
FROM data
PIVOT (
  MAX(c1) FOR day IN ([1], [2], ..., [30], [31])
) p
;

共通テーブル式を使用して、指定された月のデータを別のステップとして準備し、PIVOT構文を使用して集計して結果をピボットします。

上記は、SELECTステートメントでもある単一のステートメントを使用してジョブ全体を実行することに注意してください。つまり、マルチステートメントTVFをインラインTVFに変換できるということです。

IF OBJECT_ID('dba.fnu_data') IS NOT NULL
  DROP FUNCTION dba.fnu_data
GO
CREATE FUNCTION dba.fnu_data(@period varchar(6))
RETURNS TABLE
RETURN (
  WITH data AS (
    SELECT
      user_id,
      DAY([when]) AS day,
      c1
    FROM [table] t
    CROSS APPLY (
      SELECT CAST(@period + '01' AS date)
    ) x (startdate)
    WHERE t.day >= x.startdate
      AND t.day <  DATEADD(MONTH, 1, startdate)
  )
  SELECT
    id,
    CAST([1] AS varchar(30)) AS Day01,
    ...
    CAST([31] AS varchar(30)) AS Day31
  FROM data
  PIVOT (
    MAX(c1) FOR day IN ([1], [2], ..., [30], [31])
  ) p
)
GO

インラインTVFには、関数が呼び出されているクエリ全体を考慮してプランが選択されるという点で、マルチステートメントTVFよりも優れているという利点があります。インラインTVFは、この点でビューのようなものです。

マルチステートメントTVFとインラインTVFはSQLServerの異なる種類のオブジェクトであり、一方を他方に変更することはできないため、変換は上記のように+を使用して実行する必要があることに注意してください。DROPCREATE

于 2013-02-05T08:06:42.623 に答える
0

私はあなたの問題が物理的な挿入であるとは思いません...むしろ挿入のための最終的な選択です。

この特定のクエリは、結果セットを生成するために少なくとも30の個別のSELECTを実行し、それをテーブル変数に入力し直すため、ボトルネックになります。

SELECT
    user_id,
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 0, @day)) Day01,
    ...    
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 30, @day)) Day31
FROM
    table

このクエリから正確に何を達成しようとしているのか説明していただけますか。

于 2013-02-05T07:16:20.377 に答える