2

2005バージョン以降、MSSQLServerスクリプトのROW_NUMBER関数を利用することに慣れています。しかし、この関数を使用して大きなテーブルをクエリすると、パフォーマンスが大幅に低下することに気付きました。

4つの列を持つテーブルを想像してください(外部データベースの実際のテーブルにはより多くの列がありますが、例の複雑さを避けるためにそれらだけを使用しました):

DECLARE TABLE StockItems (
  Id int PRIMARY KEY IDENTITY(1,1),
  StockNumber nvarchar(max),
  Name nvarchar(max),
  [Description] nvarchar(max))

次のパラメータを使用して、200000以上の行で埋められたこのテーブルをクエリする手順を記述しました。

  • @SortExpression-並べ替える列の名前
  • @SortDirection-ビット情報(0 =昇順、1 =降順)
  • @startRowIndex-行を取得するゼロベースのインデックス
  • @maximumRows-取得する行数

クエリ:

SELECT sortedItems.Id
    ,si.StockNumber
    ,si.Name
    ,si.Description
FROM (SELECT s.Id
         ,CASE WHEN @SortDirection=1 THEN
            CASE
               WHEN CHARINDEX('Name',@SortExpression)=1 THEN
                 ROW_NUMBER() OVER (ORDER by s.Name DESC)
               WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN
                 ROW_NUMBER() OVER (ORDER by s.StockNumber DESC)
            ELSE ROW_NUMBER() OVER (ORDER by s.StockNumber DESC)
            END
          ELSE    
            CASE
               WHEN CHARINDEX('Name',@SortExpression)=1 THEN
                  ROW_NUMBER() OVER (ORDER by s.Name ASC)
               WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN
                  ROW_NUMBER() OVER (ORDER by s.StockNumber ASC)
            ELSE  ROW_NUMBER() OVER (ORDER by s.StockNumber ASC)
            END
          END AS RowNo
       FROM stockItems s
     ) as sortedItems
INNER JOIN StockItems si ON sortedItems.Id=si.Id
ORDER BY sortedItems.RowNo

行数が急速に増加している状況では、すべての行をソートする必要があるため、ROW_NUMBERは無効になりました。

このパフォーマンスの不利を回避し、クエリを高速化するのを手伝ってもらえますか?

4

3 に答える 3

2

実行パスを確認してください。ROW_NUMBER()正しいインデックスがあれば、大きな影響はありません。クエリの問題はにありませんROW_NUMBER()。代わりに動的を使用してください。これにより、によって引き起こされる2つのセグメンテーションが排除されROW_NUMBER()ます。これを400万を超えるレコードテーブルでテストしたところ、一瞬で返されます。

DECLARE @SortExpression VARCHAR(32)  SET @SortExpression = 'StockNumber'
DECLARE @SortDirection BIT           SET @SortDirection  = 1
DECLARE @startRowIndex BIGINT        SET @startRowIndex  = 1000
DECLARE @maximumRows BIGINT          SET @maximumRows    = 5000

DECLARE @vsSQL AS NVARCHAR(MAX)
SET @vsSQL = ''
SET @vsSQL = @vsSQL + 'SELECT sortedItems.Id, sortedItems.StockNumber, sortedItems.Name, sortedItems.Description FROM ( '
SET @vsSQL = @vsSQL + 'SELECT s.Id, s.StockNumber, s.Name, s.Description, '
SET @vsSQL = @vsSQL + 'ROW_NUMBER() OVER (ORDER BY ' + @SortExpression + ' ' + CASE @SortDirection WHEN 1 THEN 'DESC' ELSE 'ASC' END + ') AS RowNo '
SET @vsSQL = @vsSQL + 'FROM StockItems s '
SET @vsSQL = @vsSQL + ') AS sortedItems '
SET @vsSQL = @vsSQL + 'WHERE RowNo BETWEEN ' + CONVERT(VARCHAR,@startRowIndex) + ' AND ' + CONVERT(VARCHAR,@startRowIndex+@maximumRows) + ' '
SET @vsSQL = @vsSQL + 'ORDER BY sortedItems.RowNo'

PRINT @vsSQL
EXEC sp_executesql @vsSQL
于 2013-03-26T13:18:57.330 に答える
0

ケース式をorder by句に移動できます。

order by (case when @SortDirection=1 and CHARINDEX('Name',@SortExpression)=1 then s.name end) desc,
         (case when @SortDirection=1 and CHARINDEX('StockNumber',@SortExpression)=1 then s.StockNumber end) desc,
         (case when @SortDirection=1 and (CHARINDEX('StockNumber',@SortExpression)<>1 and CHARINDEX('Name',@SortExpression)<>1) then va.match end) desc,
         (case when @SortDirection<>1 and CHARINDEX('Name',@SortExpression)=1 then s.name end) asc,
         (case when @SortDirection<>1 and CHARINDEX('StockNumber',@SortExpression)=1 then s.StockNmber end) asc,
         (case when @SortDirection<>1 and (CHARINDEX('StockNumber',@SortExpression)<>1 and CHARINDEX('Name',@SortExpression)<>1) then va.match end) asc

式にが含まれていることに気付いたva.matchので、クエリ内のどのテーブルとも実際には一致しません。だから、私はただorder by表現を入れているだけです。

そして、はい、テーブルが大きくなるにつれて、これにはより多くの時間がかかります。order byがより効率的かどうかはわかりませんが、row_number()可能です。

行を並べ替える必要がある場合は、何らかの方法で並べ替えを行う必要があります(おそらく、代わりにインデックスを使用できます)。注文を気にしない場合は、次の方法でチャンスをつかむことができます。

row_number() over (order by (select NULL))

SQL Serverでは、これにより、個別の並べ替えなしで連番が割り当てられることがわかりました。ただし、これは保証されていません(この使用をサポートするドキュメントは見つかりませんでした)。また、結果は実行ごとに必ずしも安定しているとは限りません。

于 2013-03-26T13:53:07.547 に答える
0

大きな結果セットに対してROW_NUMBER()関数を使用して、パフォーマンスの低下を回避する方法を見つけました。私が質問に書いていなかった目標は、クエリをnvarchar変数として宣言して実行することを避けることでした。これは、SQLインジェクションの扉を開く可能性があるためです。

したがって、解決策は、必要な並べ替え順序で可能な限りデータをクエリし、次に結果セットをクエリして順序を切り替え、現在のページのデータのみを取得することです。最後に、逆の順序で並べ替えた結果を取得して、もう一度並べ替えることができます。

新しい変数@innerCountを定義して、ほとんどの内部結果セットをクエリし、クエリクライアントが@sortExpression変数と@sortDirection変数で指定するように順序付けます。

SET @innerCount = @startRowIndex + @maximumRows

Select OppositeQuery.Id
,s.StockNumber
,s.Name
,s.Description
FROM (SELECT TOP (@maximumRows) InnerItems.Id
       FROM
            (SELECT TOP (@innerCount) sti.Id
               FROM stockItems sti
               ORDER BY
                CASE WHEN @SortDirection=1 THEN
                    CASE
                        WHEN CHARINDEX('Name',@SortExpression)=1 THEN sti.Name
                        WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN sti.StockNumber
                        ELSE sti.StockNumber
                    END
                END DESC
                CASE WHEN ISNULL(@SortDirection,0)=0 THEN
                    CASE
                       WHEN CHARINDEX('Name',@SortExpression)=1 THEN sti.Name
                       WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN sti.StockNumber
                       ELSE sti.StockNumber
                    END
                END ASC
             ) as InnerQuery
          INNER JOIN StockItems si on InnerQuery.Id=si.Id
          ORDER BY
            CASE WHEN @SortDirection=1 then
                CASE
                   WHEN CHARINDEX('Name',@SortExpression)=1 THEN si.Name
                   WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN si.StockNumber
                   ELSE si.StockNumber
               END
            END ASC
            CASE WHEN ISNULL(@SortDirection,0)=0 then
                CASE
                   WHEN CHARINDEX('Name',@SortExpression)=1 THEN si.Name
                   WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN si.StockNumber
                   ELSE si.StockNumber
                END
            END ASC
    ) AS OppositeQuery
INNER JOIN StockItems s on OppositeQuery.Id=s.Id
ORDER BY
CASE WHEN @SortDirection=1 THEN
    CASE
        WHEN CHARINDEX('Name',@SortExpression)=1 THEN s.Name
        WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN s.StockNumber
        ELSE s.StockNumber
     END
END DESC
CASE WHEN ISNULL(@SortDirection,0)=0 THEN
    CASE
        WHEN CHARINDEX('Name',@SortExpression)=1 THEN s.Name
        WHEN CHARINDEX('StockNumber',@SortExpression)=1 THEN s.StockNumber
        ELSE s.StockNumber
    END
END ASC

このアプローチの欠点は、データを3回ソートする必要があることですが、StockItemsテーブルへの複数の内部結合の場合、サブクエリはROW_NUMBER()関数を使用するよりもはるかに高速です。

助けてくれたすべての貢献者に感謝します。

于 2013-03-26T14:39:18.243 に答える