문제

특정 유형의 SQL 쿼리의 경우 보조 숫자 테이블이 매우 유용할 수 있습니다.특정 작업에 필요한 만큼의 행이 포함된 테이블로 생성되거나 각 쿼리에 필요한 행 수를 반환하는 사용자 정의 함수로 생성될 수 있습니다.

그러한 기능을 생성하는 최적의 방법은 무엇입니까?

도움이 되었습니까?

해결책

ㅎ...죄송합니다. 오래된 게시물에 답변이 너무 늦어졌습니다.그리고 예, 이 스레드에서 가장 인기 있는 답변(당시 14가지 다른 방법에 대한 링크가 있는 Recursive CTE 답변)이 음...이기 때문에 응답해야 했습니다.최고의 성능에 도전했습니다.

첫째, 14개의 서로 다른 솔루션이 포함된 기사는 숫자/탈리 테이블을 즉석에서 생성하는 다양한 방법을 보는 데 적합하지만 기사와 인용 스레드에서 지적한 대로 매우 중요한 인용문...

"효율성과 성능에 관한 제안은 종종 주관적입니다.쿼리 사용 방법에 관계없이 물리적 구현은 쿼리의 효율성을 결정합니다.따라서 편향된 가이드 라인에 의존하기보다는 쿼리를 테스트하고 어떤 쿼리가 더 잘 수행되는지 결정해야합니다. "

아이러니하게도 기사 자체에는 주관적인 진술과 '편향된 지침'이 많이 포함되어 있습니다. "재귀적 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 프로파일러에서 얻은 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 루프에 이어 두 번째로 최악이며 While 루프보다 논리적 읽기 형태의 메모리 압박이 8배 더 높습니다..이는 스테로이드의 RBAR이며 While 루프를 피해야 하는 것처럼 단일 행 계산에 대해서는 어떤 희생을 치르더라도 피해야 합니다. 재귀가 상당히 가치 있는 곳이 있지만 이것은 그중 하나가 아닙니다..

사이드 바로 Mr.데니는 정말 완벽해...대부분의 작업에는 올바른 크기의 영구 숫자 또는 집계 테이블이 적합합니다.올바른 크기는 무엇을 의미합니까?글쎄, 대부분의 사람들은 날짜를 생성하거나 VARCHAR(8000)에서 분할을 수행하기 위해 Tally 테이블을 사용합니다."N"에 올바른 클러스터형 인덱스가 있는 11,000행 Tally 테이블을 생성하면 30년 이상의 날짜를 생성할 수 있는 충분한 행을 갖게 됩니다. ) VARCHAR(8000) 분할을 처리하기에 충분합니다."올바른 크기 조정"이 왜 그렇게 중요한가요?Tally 테이블을 많이 사용하는 경우 캐시에 쉽게 들어가므로 메모리에 큰 부담을 주지 않고 매우 빠르게 처리됩니다.

마지막으로 중요한 것은 영구 Tally 테이블을 생성하는 경우 이를 구축하는 데 어떤 방법을 사용하는지는 별로 중요하지 않다는 점입니다. 1) 테이블은 한 번만 생성되고 2) 11,000개의 행과 같을 경우 테이블에서 모든 메소드는 "충분히 양호"하게 실행될 것입니다. 그렇다면 왜 어떤 방법을 사용해야 하는지에 대해 제가 분개하는 이유는 무엇일까요???

대답은 더 잘 알지 못하고 자신의 일을 완수해야 하는 불쌍한 남자/여자가 재귀 CTE 방법과 같은 것을 보고 이를 구축보다 훨씬 더 크고 자주 사용되는 방법에 사용하기로 결정할 수 있다는 것입니다. 영구적인 Tally 테이블을 만들고 싶습니다. 그 사람들, 그들의 코드가 실행되는 서버, 그리고 그 서버의 데이터를 소유한 회사를 보호하세요..응...그건 정말 큰 일이야.그것은 다른 모든 사람들에게도 마찬가지여야 합니다."충분히 좋은 것" 대신에 일을 하는 올바른 방법을 가르치십시오.게시물이나 책의 내용을 게시하거나 사용하기 전에 몇 가지 테스트를 수행하십시오.실제로 당신이 구한 생명은 당신 자신의 것이 될 수도 있습니다. 특히 재귀 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),']'));

LiveDemo


아이디어에서 따옴 OPENJSON을 사용하여 일련의 숫자를 생성하려면 어떻게 해야 합니까?

편집하다:아래 Conrad의 의견을 참조하세요.

Jeff Moden의 답변은 훌륭합니다 ...하지만 Postgres에서 E32 행을 제거하지 않으면 Itzik 방법이 실패한다는 것을 알았습니다.

Postgres에서 약간 더 빠른(40ms 대 100ms)은 내가 찾은 또 다른 방법입니다. 여기 포스트그레스에 적합:

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보다 더 많은 읽기를 수행하지만 기존 CTE보다는 적습니다.그러나 다른 쿼리보다 지속적으로 적은 WRITES를 수행합니다.아시다시피 쓰기는 읽기보다 지속적으로 훨씬 더 비쌉니다.

지속 시간은 코어 수(MAXDOP)에 따라 크게 달라지지만 내 8core에서는 다른 쿼리보다 지속적으로 더 빠르게(ms 단위로 더 짧은 지속 시간) 수행됩니다.

나는 다음을 사용하고 있습니다 :

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, 32GB, Xeon X3450 @2.67Ghz, 4코어 HT 활성화.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top