Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

SQL, tabella ausiliaria dei numeri

Heh... scusa se rispondo così in ritardo a un vecchio post. E, sì, ho dovuto rispondere perché la risposta più popolare (all'epoca, la risposta CTE ricorsiva con il collegamento a 14 metodi diversi) su questo thread è, ummm... le prestazioni sono state al massimo sfidate.

Innanzitutto l'articolo con le 14 diverse soluzioni va bene per vedere al volo i diversi metodi per creare una tabella Numeri/Tally ma come sottolineato nell'articolo e nel thread citato, c'è un molto citazione importante...

"i suggerimenti relativi all'efficienza e alle prestazioni sono spesso soggettivi. Indipendentemente da come viene utilizzata una query, l'implementazione fisica determina l'efficienza di una query. Pertanto, anziché basarsi su linee guida distorte, è imperativo testare la query e determinare quale funziona meglio."

Ironia della sorte, l'articolo stesso contiene molte affermazioni soggettive e "linee guida distorte" come "un CTE ricorsivo può generare un elenco di numeri abbastanza efficientemente " e "Questo è un metodo efficiente di usare il ciclo WHILE da un post in un newsgroup di Itzik Ben-Gen" (che sono sicuro che ha pubblicato solo a scopo di confronto). Andiamo gente... Il solo fatto di menzionare il buon nome di Itzik può portare qualche povero sciatto a usare effettivamente quel metodo orribile. L'autore dovrebbe esercitarsi su ciò che predica e dovrebbe fare un piccolo test delle prestazioni prima di fare affermazioni così ridicolmente errate, specialmente di fronte a qualsiasi scalabilità.

Con l'idea di eseguire effettivamente dei test prima di fare affermazioni soggettive su ciò che fa qualsiasi codice o su ciò che "piace a qualcuno", ecco del codice con cui puoi eseguire i tuoi test. Configura il profiler per lo SPID da cui stai eseguendo il test e verifica tu stesso... fai semplicemente un "Search'n'Replace" del numero 1000000 per il tuo numero "preferito" e vedi...

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

Già che ci siamo, ecco i numeri che ottengo da SQL Profiler per i valori di 100, 1000, 10000, 100000 e 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

Come puoi vedere, il metodo CTE ricorsivo è il secondo peggiore solo al ciclo While per durata e CPU e ha una pressione di memoria 8 volte superiore sotto forma di letture logiche rispetto al ciclo While . È RBAR sotto steroidi e dovrebbe essere evitato, a tutti i costi, per qualsiasi calcolo di riga singola proprio come dovrebbe essere evitato un ciclo While. Ci sono luoghi in cui la ricorsione è piuttosto preziosa, ma questo NON È uno di questi .

Come barra laterale, il signor Denny è assolutamente perfetto... un tavolo Numbers o Tally permanente di dimensioni corrette è la strada da percorrere per la maggior parte delle cose. Cosa significa taglia corretta? Bene, la maggior parte delle persone usa una tabella Tally per generare date o per fare divisioni su VARCHAR(8000). Se crei una tabella Tally di 11.000 righe con l'indice raggruppato corretto su "N", avrai abbastanza righe per creare più di 30 anni di date (lavoro un bel po' con i mutui, quindi 30 anni sono un numero chiave per me ) e certamente sufficiente per gestire una divisione VARCHAR(8000). Perché la "taglia giusta" è così importante? Se la tabella Tally viene utilizzata molto, si inserisce facilmente nella cache, il che la rende incredibilmente veloce senza troppa pressione sulla memoria.

Ultimo ma non meno importante, tutti sanno che se crei una tabella Tally permanente, non importa quale metodo usi per costruirla perché 1) verrà creata solo una volta e 2) se è qualcosa come una riga di 11.000 table, tutti i metodi funzioneranno "abbastanza bene". Allora perché tutta l'indiginazione da parte mia su quale metodo usare???

La risposta è che qualche povero ragazzo/ragazza che non sa niente di meglio e ha solo bisogno di portare a termine il proprio lavoro potrebbe vedere qualcosa come il metodo CTE ricorsivo e decidere di usarlo per qualcosa di molto più grande e molto più frequentemente utilizzato rispetto alla costruzione una tabella Tally permanente e sto cercando di proteggere quelle persone, i server su cui gira il loro codice e la società che possiede i dati su quei server . Sì... è un grosso problema. Dovrebbe esserlo anche per tutti gli altri. Insegna il modo giusto di fare le cose invece di "abbastanza buono". Fai alcuni test prima di pubblicare o utilizzare qualcosa da un post o un libro... la vita che salvi potrebbe, infatti, essere la tua, soprattutto se pensi che un CTE ricorsivo sia la strada da percorrere per qualcosa del genere.;-)

Grazie per l'ascolto...