59

特定の種類のSQLクエリでは、数値の補助テーブルが非常に役立ちます。これは、特定のタスクに必要な数の行を含むテーブルとして、または各クエリで必要な行数を返すユーザー定義関数として作成できます。

そのような関数を作成するための最適な方法は何ですか?

4

7 に答える 7

112

ええと...すみません、古い投稿への返信が遅くなりました。そして、ええ、私は応答しなければなりませんでした。なぜなら、このスレッドで最も人気のある答え(当時、14の異なるメソッドへのリンクを含む再帰CTEの答え)は、うーん...パフォーマンスがせいぜい挑戦されたからです。

まず、14の異なるソリューションを含む記事は、Numbers / Tallyテーブルをその場で作成するさまざまな方法を確認するのに適していますが、記事と引用されたスレッドで指摘されているように、非常に重要な引用があります...

「効率とパフォーマンスに関する提案は主観的なものであることがよくあります。クエリの使用方法に関係なく、物理的な実装によってクエリの効率が決まります。したがって、偏ったガイドラインに頼るのではなく、クエリをテストしてどれを決定するかが不可欠です。パフォーマンスが向上します。」

皮肉なことに、記事自体には多くの主観的なステートメントと「再帰CTEはかなり効率的に番号リストを生成できる」「これはItzikBen-Genによるニュースグループ投稿からのWHILEループを使用する効率的な方法です」などの「偏ったガイドライン」が含まれています(比較のためだけに投稿したと思います)。さあ、みんな... Itzikの良い名前に言及するだけで、貧しいスロブが実際にその恐ろしい方法を使用するようになるかもしれません。作者は、彼が説教することを実践し、特にスカラビリティに直面して、そのようなばかげて間違った発言をする前に、少しのパフォーマンステストを行う必要があります。

コードが何をするのか、誰かが「好き」なのかについて主観的な主張をする前に、実際にいくつかのテストを行うことを考えて、ここにあなたがあなた自身のテストを行うことができるいくつかのコードがあります。テストを実行しているSPIDのプロファイラーをセットアップし、自分でチェックしてください...番号1000000の「Search'n'Replace」を実行して、「お気に入り」の番号を確認してください...

--===== Test for 1000000 rows ==================================
GO
--===== Traditional RECURSIVE CTE method
   WITH Tally (N) AS 
        ( 
         SELECT 1 UNION ALL 
         SELECT 1 + N FROM Tally WHERE N < 1000000 
        ) 
 SELECT N 
   INTO #Tally1 
   FROM Tally 
 OPTION (MAXRECURSION 0);
GO
--===== Traditional WHILE LOOP method
 CREATE TABLE #Tally2 (N INT);
    SET NOCOUNT ON;
DECLARE @Index INT;
    SET @Index = 1;
  WHILE @Index <= 1000000 
  BEGIN 
         INSERT #Tally2 (N) 
         VALUES (@Index);
            SET @Index = @Index + 1;
    END;
GO
--===== Traditional CROSS JOIN table method
 SELECT TOP (1000000)
        ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS N
   INTO #Tally3
   FROM Master.sys.All_Columns ac1
  CROSS JOIN Master.sys.ALL_Columns ac2;
GO
--===== Itzik's CROSS JOINED CTE method
   WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
        E02(N) AS (SELECT 1 FROM E00 a, E00 b),
        E04(N) AS (SELECT 1 FROM E02 a, E02 b),
        E08(N) AS (SELECT 1 FROM E04 a, E04 b),
        E16(N) AS (SELECT 1 FROM E08 a, E08 b),
        E32(N) AS (SELECT 1 FROM E16 a, E16 b),
   cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
 SELECT N
   INTO #Tally4
   FROM cteTally
  WHERE N <= 1000000;
GO
--===== Housekeeping
   DROP TABLE #Tally1, #Tally2, #Tally3, #Tally4;
GO

その間、SQL Profilerから取得した100、1000、10000、100000、および1000000の値を示します...

SPID TextData                                 Dur(ms) CPU   Reads   Writes
---- ---------------------------------------- ------- ----- ------- ------
  51 --===== Test for 100 rows ==============       8     0       0      0
  51 --===== Traditional RECURSIVE CTE method      16     0     868      0
  51 --===== Traditional WHILE LOOP method CR      73    16     175      2
  51 --===== Traditional CROSS JOIN table met      11     0      80      0
  51 --===== Itzik's CROSS JOINED CTE method        6     0      63      0
  51 --===== Housekeeping   DROP TABLE #Tally      35    31     401      0

  51 --===== Test for 1000 rows =============       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method      47    47    8074      0
  51 --===== Traditional WHILE LOOP method CR      80    78    1085      0
  51 --===== Traditional CROSS JOIN table met       5     0      98      0
  51 --===== Itzik's CROSS JOINED CTE method        2     0      83      0
  51 --===== Housekeeping   DROP TABLE #Tally       6    15     426      0

  51 --===== Test for 10000 rows ============       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method     434   344   80230     10
  51 --===== Traditional WHILE LOOP method CR     671   563   10240      9
  51 --===== Traditional CROSS JOIN table met      25    31     302     15
  51 --===== Itzik's CROSS JOINED CTE method       24     0     192     15
  51 --===== Housekeeping   DROP TABLE #Tally       7    15     531      0

  51 --===== Test for 100000 rows ===========       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method    4143  3813  800260    154
  51 --===== Traditional WHILE LOOP method CR    5820  5547  101380    161
  51 --===== Traditional CROSS JOIN table met     160   140     479    211
  51 --===== Itzik's CROSS JOINED CTE method      153   141     276    204
  51 --===== Housekeeping   DROP TABLE #Tally      10    15     761      0

  51 --===== Test for 1000000 rows ==========       0     0       0      0
  51 --===== Traditional RECURSIVE CTE method   41349 37437 8001048   1601
  51 --===== Traditional WHILE LOOP method CR   59138 56141 1012785   1682
  51 --===== Traditional CROSS JOIN table met    1224  1219    2429   2101
  51 --===== Itzik's CROSS JOINED CTE method     1448  1328    1217   2095
  51 --===== Housekeeping   DROP TABLE #Tally       8     0     415      0

ご覧のとおり、再帰CTEメソッドは、DurationおよびCPUのWhileループに次ぐ最悪であり、Whileループの8倍の論理読み取り形式のメモリプレッシャーがあります。これはステロイドのRBARであり、Whileループを回避する必要があるのと同じように、単一行の計算では絶対に回避する必要があります。 再帰が非常に価値のある場所がありますが、これはその1つではありません

サイドバーとして、デニー氏は絶対に注目を集めています...正しいサイズのパーマネントナンバーまたはタリーテーブルは、ほとんどのことを行うための方法です。正しいサイズとはどういう意味ですか?ほとんどの人は、Tallyテーブルを使用して日付を生成したり、VARCHAR(8000)で分割を行ったりします。「N」に正しいクラスター化インデックスを使用して11,000行のTallyテーブルを作成すると、30年以上の日付を作成するのに十分な行があります(私は住宅ローンをかなり扱っているので、30年が重要な数値です)そして確かにVARCHAR(8000)分割を処理するのに十分です。「適切なサイズ設定」が非常に重要なのはなぜですか。Tallyテーブルが頻繁に使用される場合、キャッシュに簡単に収まるため、メモリに大きな負担をかけることなく、非常に高速になります。

最後になりましたが、永続的なTallyテーブルを作成する場合、1)1回だけ作成され、2)11,000行のようなものであるため、どの方法を使用して作成してもかまいません。表では、すべてのメソッドが「十分に」実行されます。 では、なぜどの方法を使用するかについての私の側のすべての憤慨?

答えは、よくわからず、仕事を終わらせる必要がある貧しい人/ギャルが、再帰CTEメソッドのようなものを見て、構築よりもはるかに大きく、はるかに頻繁に使用されるものに使用することを決定する可能性があるということです永続的なTallyテーブルであり、私はそれらの人々、彼らのコードが実行されているサーバー、およびそれらのサーバー上のデータを所有している会社を保護しようとしています。ええ...それは大したことです。それは他のすべての人にも当てはまるはずです。「十分に良い」のではなく、物事を行う正しい方法を教えてください。投稿や本から何かを投稿したり使用したりする前に、いくつかのテストを行ってください...実際、再帰CTEがこのようなものを選択する方法であると考える場合は、実際、節約できる命はあなた自身のものかもしれません。;-)

聞いてくれてありがとう...

于 2010-04-18T17:44:31.607 に答える
11

最も最適な関数は、関数の代わりにテーブルを使用することです。関数を使用すると、特に返される値が非常に広い範囲をカバーする場合に、返されるデータの値を作成するために余分な CPU 負荷が発生します。

于 2008-09-02T09:48:19.837 に答える
5

この記事では、14の異なる解決策をそれぞれについて説明します。重要な点は次のとおりです。

効率とパフォーマンスに関する提案は、多くの場合主観的です。クエリがどのように使用されているかに関係なく、物理的な実装によってクエリの効率が決まります。したがって、偏ったガイドラインに依存するのではなく、クエリをテストして、どちらがパフォーマンスが優れているかを判断することが不可欠です。

私は個人的に好きでした:

WITH Nbrs ( n ) AS (
    SELECT 1 UNION ALL
    SELECT 1 + n FROM Nbrs WHERE n < 500 )
SELECT n FROM Nbrs
OPTION ( MAXRECURSION 500 )
于 2009-09-25T19:50:34.407 に答える
3

このビューは超高速で、すべて正のint値が含まれています。

CREATE VIEW dbo.Numbers
WITH SCHEMABINDING
AS
    WITH Int1(z) AS (SELECT 0 UNION ALL SELECT 0)
    , Int2(z) AS (SELECT 0 FROM Int1 a CROSS JOIN Int1 b)
    , Int4(z) AS (SELECT 0 FROM Int2 a CROSS JOIN Int2 b)
    , Int8(z) AS (SELECT 0 FROM Int4 a CROSS JOIN Int4 b)
    , Int16(z) AS (SELECT 0 FROM Int8 a CROSS JOIN Int8 b)
    , Int32(z) AS (SELECT TOP 2147483647 0 FROM Int16 a CROSS JOIN Int16 b)
    SELECT ROW_NUMBER() OVER (ORDER BY z) AS n
    FROM Int32
GO
于 2011-07-04T12:24:12.060 に答える
1

を使用して、使用SQL Server 2016+できる数値テーブルを生成しますOPENJSON

-- range from 0 to @max - 1
DECLARE @max INT = 40000;

SELECT rn = CAST([key] AS INT) 
FROM OPENJSON(CONCAT('[1', REPLICATE(CAST(',1' AS VARCHAR(MAX)),@max-1),']'));

LiveDemo


How can we use OPENJSON to generate series of numbers? からのアイデア

于 2016-05-02T16:29:41.807 に答える
0

さらに後で、少し異なる「従来の」CTE を提供したいと思います (行のボリュームを取得するためにベース テーブルには触れません)。

--===== Hans CROSS JOINED CTE method
WITH Numbers_CTE (Digit)
AS
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9)
SELECT HundredThousand.Digit * 100000 + TenThousand.Digit * 10000 + Thousand.Digit * 1000 + Hundred.Digit * 100 + Ten.Digit * 10 + One.Digit AS Number
INTO #Tally5
FROM Numbers_CTE AS One CROSS JOIN Numbers_CTE AS Ten CROSS JOIN Numbers_CTE AS Hundred CROSS JOIN Numbers_CTE AS Thousand CROSS JOIN Numbers_CTE AS TenThousand CROSS JOIN Numbers_CTE AS HundredThousand

この CTE は、Itzik の CTE よりも多くの READ を実行しますが、従来の CTE よりは少なくなります。 ただし、一貫して他のクエリよりも少ない書き込みを実行します。 ご存知のように、書き込みは一貫して読み取りよりもはるかにコストがかかります。

所要時間はコア数 (MAXDOP) に大きく依存しますが、私の 8core では、他のクエリよりも一貫して高速 (ミリ秒単位で短い所要時間) に実行されます。

私は使っている:

Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 
May 14 2014 18:34:29 
Copyright (c) Microsoft Corporation
Enterprise Edition (64-bit) on Windows NT 6.3 <X64> (Build 9600: )

Windows Server 2012 R2、32 GB、Xeon X3450 @2.67Ghz、4 コア HT 対応。

于 2014-10-22T10:06:40.443 に答える
0

編集:以下のコンラッドのコメントを参照してください。

Jeff Moden の答えは素晴らしいです...しかし、Postgres では、E32 行を削除しない限り、Itzik メソッドが失敗することがわかりました。

postgres でわずかに高速 (40ms 対 100ms) は、私がここで見つけた別の方法で、postgres に適応しています。

WITH 
    E00 (N) AS ( 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 ),
    E01 (N) AS (SELECT a.N FROM E00 a CROSS JOIN E00 b),
    E02 (N) AS (SELECT a.N FROM E01 a CROSS JOIN E01 b ),
    E03 (N) AS (SELECT a.N FROM E02 a CROSS JOIN E02 b 
        LIMIT 11000  -- end record  11,000 good for 30 yrs dates
    ), -- max is 100,000,000, starts slowing e.g. 1 million 1.5 secs, 2 mil 2.5 secs, 3 mill 4 secs
    Tally (N) as (SELECT row_number() OVER (ORDER BY a.N) FROM E03 a)

SELECT N
FROM Tally

SQL Server から Postgres の世界に移行しているので、そのプラットフォームでテーブルを集計するためのより良い方法を見逃している可能性があります... INTEGER()? 順序()?

于 2011-01-20T09:28:30.397 に答える