質問
特定の種類の SQL クエリでは、補助的な数値テーブルが非常に便利です。これは、特定のタスクに必要な数の行を含むテーブルとして作成することも、各クエリで必要な行数を返すユーザー定義関数として作成することもできます。
このような関数を作成する最適な方法は何でしょうか?
解決
へー…古い投稿への返信が大変遅くなり申し訳ありません。そして、そう、このスレッドで最も人気のある答え (当時、14 の異なるメソッドへのリンクを含む Recursive CTE の答え) が、うーん...パフォーマンスはせいぜい挑戦されます。
まず、14 の異なるソリューションを掲載した記事は、その場で Numbers/Tally テーブルを作成するさまざまな方法を確認するのに適していますが、記事と引用されたスレッドで指摘されているように、 とても 重要な引用...
「効率とパフォーマンスに関する提案はしばしば主観的です。クエリの使用方法に関係なく、物理的実装によりクエリの効率が決定されます。したがって、偏ったガイドラインに依存するのではなく、クエリをテストし、どちらがより良いパフォーマンスを発揮するかを判断することが不可欠です。」
皮肉なことに、記事自体には多くの主観的な記述や、次のような「偏ったガイドライン」が含まれています。 「再帰的 CTE は数値リストを生成できます かなり効率的に" そして "これは 効率的な方法 Itzik Ben-Gen によるニュースグループ投稿からの WHILE ループの使用について」 (彼は比較目的だけで投稿したと確信しています)。さあ皆さん...Itzik の良い名前を言及するだけで、一部の哀れな怠け者が実際にその恐ろしい方法を使用するようになるかもしれません。著者は、自分が説いていることを実践し、特にスケーラビリティに直面してそのようなばかばかしいほど間違った発言をする前に、少しのパフォーマンス テストを行うべきです。
コードが何をするか、または誰かが「好き」であるかについて主観的な主張をする前に、実際にテストを行ってみるという考えで、独自のテストを実行できるコードをいくつか紹介します。テストを実行している SPID のプロファイラーをセットアップし、自分で確認してください...番号 1000000 を「お気に入り」の番号として「検索と置換」を実行して確認してください...
--===== 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 メソッドは、継続時間と CPU に関して While ループに次いで 2 番目に最悪であり、論理読み取りの形式でのメモリ負荷が While ループの 8 倍になります。. 。これは強力な RBAR であり、while ループを避ける必要があるのと同じように、単一行の計算では絶対に避けるべきです。 再帰が非常に価値のある場所はありますが、これはその 1 つではありません.
補足として、Mr.デニーはまさにその通りです...適切なサイズの永続的な Numbers テーブルまたは Tally テーブルを使用すると、ほとんどの場合に使用できます。正しいサイズとはどういう意味ですか?そうですね、ほとんどの人は Tally テーブルを使用して日付を生成したり、VARCHAR(8000) で分割を行ったりします。「N」に正しいクラスター化インデックスを使用して 11,000 行の集計テーブルを作成すると、30 年分以上の日付を作成するのに十分な行が得られます (私は住宅ローンを扱う仕事が多いので、30 年は私にとって重要な数字です) )、VARCHAR(8000) 分割を処理するには十分です。なぜ「適切なサイズ」がそれほど重要なのでしょうか?Tally テーブルが頻繁に使用される場合、キャッシュに簡単に収まるため、メモリにまったく負担をかけずに非常に高速になります。
最後になりますが、永続的な Tally テーブルを作成する場合、1) 作成されるのは 1 回だけで、2) 行数が 11,000 行程度である場合、永続的な Tally テーブルを作成するのにどの方法を使用するかはあまり重要ではないことは誰もが知っています。表に示すように、すべてのメソッドは「十分に」実行されます。 では、なぜ私がどの方法を使用するかについて憤慨するのでしょうか?
答えは、それ以上の知識がなく、ただ自分の仕事を終わらせる必要がある哀れな男性/女性が、再帰的 CTE 手法のようなものを見て、建築よりもはるかに大規模で、より頻繁に使用される何かにそれを使用することを決心する可能性があるということです。永続的な集計テーブルを作成しようとしています それらの人々、コードが実行されるサーバー、およびそれらのサーバー上のデータを所有する企業を保護します。. 。うん...それはそれほど大きなことなのです。それは他の人たちにとっても同様であるはずです。「十分に良い」のではなく、物事を行うための正しい方法を教えてください。投稿や書籍から何かを投稿したり使用したりする前に、いくつかのテストを行ってください...特にこのようなことを行うには再帰的 CTE が最適であると考えている場合は、あなたが救った命が実際にはあなた自身のものになる可能性があります。;-)
聞いてくれてありがとう...
他のヒント
最も最適な機能は、関数の代わりにテーブルを使用することです。関数を使用すると、特に返される値が非常に広範囲にわたる場合、返されるデータの値を作成するために余分な CPU 負荷が発生します。
この記事 14 の異なる解決策がそれぞれについて説明されています。重要な点は次のとおりです。
効率とパフォーマンスに関する提案はしばしば主観的です。クエリの使用方法に関係なく、物理的実装によりクエリの効率が決定されます。したがって、偏ったガイドラインに依存するのではなく、クエリをテストし、どちらがより良いパフォーマンスを発揮するかを判断することが不可欠です。
個人的に気に入ったのは:
WITH Nbrs ( n ) AS (
SELECT 1 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 500 )
SELECT n FROM Nbrs
OPTION ( MAXRECURSION 500 )
このビューは非常に高速で、すべての肯定的な内容が含まれています 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
使用する 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),']'));
から得られたアイデア OPENJSON を使用して一連の数値を生成するにはどうすればよいでしょうか?
編集:以下のコンラッドのコメントを参照してください。
Jeff Modenの答えは素晴らしいです...しかし、Postgres では、E32 行を削除しないと Itzik メソッドが失敗することがわかりました。
postgres でわずかに高速 (40 ミリ秒対 100 ミリ秒) は、私が見つけた別の方法です。 ここ 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 の世界に移行しているので、そのプラットフォームで集計テーブルを実行するより良い方法を見逃していたかもしれません...整数()?順序()?
まだずっと後ですが、少し異なる「従来の」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 よりは少ないです。ただし、他のクエリよりも一貫して WRITES の実行回数が少なくなります。ご存知のとおり、書き込みは常に読み取りよりもはるかに高価です。
所要時間はコア数 (MAXDOP) に大きく依存しますが、私の 8 コアでは、他のクエリよりも一貫して高速に実行されます (ミリ秒単位での所要時間が短くなります)。
使っています:
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 対応。