sql >> Databasteknik >  >> RDS >> Sqlserver

SQL, hjälptalstabell

Heh... förlåt att jag svarar så sent på ett gammalt inlägg. Och, ja, jag var tvungen att svara eftersom det mest populära svaret (vid den tiden, det rekursiva CTE-svaret med länken till 14 olika metoder) i den här tråden är, ummm... prestanda utmanad i bästa fall.

För det första är artikeln med de 14 olika lösningarna bra för att se de olika metoderna för att skapa en siffror/tallytabell i farten, men som påpekats i artikeln och i den citerade tråden finns det en mycket viktigt citat...

"Förslag angående effektivitet och prestanda är ofta subjektiva. Oavsett hur en fråga används, avgör den fysiska implementeringen effektiviteten av en fråga. Därför är det, i stället för att förlita sig på opartiska riktlinjer, absolut nödvändigt att du testar frågan och avgör vilken som ger bäst resultat."

Ironiskt nog innehåller artikeln i sig många subjektiva påståenden och "partiska riktlinjer" såsom "en rekursiv CTE kan generera en nummerlista ganska effektivt " och "Detta är en effektiv metod av att använda WHILE-slingan från ett nyhetsgruppsinlägg av Itzik Ben-Gen" (som jag är säker på att han postade bara i jämförelsesyfte). Kom igen gott folk... Att bara nämna Itziks goda namn kan leda till att någon stackars slarv faktiskt använder den där hemska metoden. Författaren bör öva på vad han predikar och bör göra ett litet prestationstest innan han gör sådana löjligt felaktiga påståenden, särskilt inför eventuell skalbarhet.

Med tanken på att faktiskt göra några tester innan du gör några subjektiva påståenden om vad någon kod gör eller vad någon "gillar", här är en kod du kan göra dina egna tester med. Ställ in profiler för SPID du kör testet från och kolla upp det själv... gör bara en "Search'n'Replace" av numret 1000000 för ditt "favorit" nummer och se...

--===== 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

Medan vi håller på, här är siffrorna jag får från SQL Profiler för värdena 100, 1000, 10000, 100000 och 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

Som du kan se är den rekursiva CTE-metoden den näst sämsta bara till While Loop för Duration och CPU och har 8 gånger minnestrycket i form av logiska läsningar än While Loop . Det är RBAR på steroider och bör undvikas, till varje pris, för alla enstaka radberäkningar precis som en While Loop bör undvikas. Det finns platser där rekursion är ganska värdefull men det här ÄR INTE en av dem .

Som en sidobar är Mr. Denny helt på topp... ett permanent nummer- eller taltabell med korrekt storlek är vägen att gå för det mesta. Vad betyder rätt storlek? Tja, de flesta använder en Tally-tabell för att generera datum eller för att göra delningar på VARCHAR(8000). Om du skapar en tabell med 11 000 rader med rätt klustrade index på "N", kommer du att ha tillräckligt många rader för att skapa mer än 30 års datum (jag jobbar med bolån en del så 30 år är ett nyckeltal för mig ) och säkerligen tillräckligt för att hantera en VARCHAR(8000) split. Varför är "rätt storlek" så viktigt? Om Tally-bordet används mycket passar det lätt i cachen vilket gör det blixtrande snabbt utan mycket press på minnet alls.

Sist men inte minst, alla vet att om du skapar en permanent Tally-tabell spelar det ingen större roll vilken metod du använder för att bygga den eftersom 1) den bara kommer att göras en gång och 2) om den är ungefär en 11 000 rad tabell, kommer alla metoder att fungera "tillräckligt bra". Så varför all indigination från min sida om vilken metod jag ska använda???

Svaret är att någon stackars kille/tjej som inte vet bättre och bara behöver få sitt jobb gjort kan se något som den rekursiva CTE-metoden och bestämmer sig för att använda den för något mycket större och mycket mer frekvent använt än att bygga. en permanent Tally-tabell och jag försöker skydda dessa personer, servrarna deras kod körs på och företaget som äger data på dessa servrar . Ja... det är så stor sak. Det borde vara för alla andra också. Lär ut rätt sätt att göra saker istället för "tillräckligt bra". Gör några tester innan du postar eller använder något från ett inlägg eller en bok... livet du räddar kan faktiskt vara ditt eget, särskilt om du tror att en rekursiv CTE är rätt väg att gå för något sådant här.;-)

Tack för att du lyssnade...



  1. Förbättrat skript som returnerar alla egenskaper från SERVERPROPERTY() i SQL Server

  2. Hur man installerar MariaDB på Rocky Linux och AlmaLinux

  3. Hur man gör anslutning till Postgres via Node.js

  4. Skript för att döda alla anslutningar till en databas (mer än RESTRICTED_USER ROLLBACK)