Perché le tabelle dei numeri sono "preziose"?


112

Il nostro esperto di database residente ci sta dicendo che le tabelle dei numeri sono preziose . Non capisco bene perché. Ecco una tabella numerica:

USE Model
GO

CREATE TABLE Numbers
(
    Number INT NOT NULL,
    CONSTRAINT PK_Numbers 
        PRIMARY KEY CLUSTERED (Number)
        WITH FILLFACTOR = 100
)

INSERT INTO Numbers
SELECT
    (a.Number * 256) + b.Number AS Number
FROM 
    (
        SELECT number
        FROM master..spt_values
        WHERE 
            type = 'P'
            AND number <= 255
    ) a (Number),
    (
        SELECT number
        FROM master..spt_values
        WHERE 
            type = 'P'
            AND number <= 255
    ) b (Number)
GO

Per il post sul blog, la logica fornita è

Le tabelle dei numeri sono davvero preziose. Li uso sempre per manipolare le stringhe, simulare le funzioni della finestra, popolare tabelle di test con molti dati, eliminare la logica del cursore e molte altre attività che sarebbero incredibilmente difficili senza di loro.

Ma non capisco esattamente quali siano questi usi - puoi fornire alcuni esempi convincenti e specifici di dove una "tabella numerica" ​​ti fa risparmiare un sacco di lavoro in SQL Server - e perché dovremmo averli?


3
Molti casi d'uso per una tabella di numeri possono essere ugualmente soddisfatti da un CTE ricorsivo che genera i numeri necessari al volo. Tuttavia, vi è una penalità per le prestazioni e alcune altre limitazioni all'approccio CTE.
Nick Chammas,

4
@Nick: direi che una tabella numerica basata su CTE al volo rispetto a una tabella fisica è solo un dettaglio di implementazione di come si genera la tabella numerica. Potato vs. Potato ...
Remus Rusanu,

1
@Remus - Sì. Volevo solo sottolineare questa alternativa a Jeff.
Nick Chammas,

2
Ho una dozzina di risposte utilizzando una tabella numerica su SO stackoverflow.com/search?q=user%3A27535+%2B%22numbers+table%22 .
gbn

Risposte:


82

Ho visto molti usi quando è necessario proiettare "dati mancanti". Per esempio. hai una serie temporale (un registro di accesso per esempio) e vuoi mostrare il numero di hit al giorno negli ultimi 30 giorni (pensa alla dashboard di analisi). Se lo fai select count(...) from ... group by dayotterrai il conteggio per ogni giorno, ma il risultato avrà solo una riga per ogni giorno in cui hai avuto almeno un accesso. D'altra parte, se prima proietti una tabella di giorni dalla tabella dei numeri ( select dateadd(day, -number, today) as day from numbers) e poi lasci unirti ai conteggi (o applicare esterno, qualunque cosa tu voglia), otterrai un risultato che ha 0 per il conteggio per i giorni in cui non aveva accesso. Questo è solo un esempio. Naturalmente, si potrebbe sostenere che il livello di presentazione della dashboard potrebbe gestire i giorni mancanti e invece mostrare solo uno 0, ma alcuni strumenti (ad esempio SSRS) non saranno semplicemente in grado di gestirlo.

Altri esempi che ho visto hanno usato trucchi di serie temporali simili (data / ora +/- numero) per eseguire tutti i tipi di calcoli delle finestre. In generale, ogni volta che in un linguaggio imperativo useresti un ciclo for con un numero ben noto di iterazioni, la natura dichiarativa e impostata di SQL può usare un trucco basato su una tabella numerica.

A proposito, sento il bisogno di sottolineare il fatto che anche se usando una tabella dei numeri sembra un'esecuzione procedurale imperativa, non cadere nell'errore di presumere che sia imperativo. Lasciami fare un esempio:

int x;
for (int i=0;i<1000000;++i)
  x = i;
printf("%d",x);

Questo programma produrrà 999999, che è praticamente garantito.

Proviamo lo stesso in SQL Server, usando una tabella numerica. Innanzitutto crea una tabella di 1.000.000 di numeri:

create table numbers (number int not null primary key);
go

declare @i int = 0
    , @j int = 0;

set nocount on;
begin transaction
while @i < 1000
begin
    set @j = 0;
    while @j < 1000
    begin
        insert into numbers (number) 
            values (@j*1000+@i);
        set @j += 1;
    end
    commit;
    raiserror (N'Inserted %d*1000', 0, 0, @i)
    begin transaction;
    set @i += 1;
end
commit
go

Ora facciamo il 'for loop':

declare @x int;
select @x = number 
from numbers with(nolock);
select @x as [@x];

Il risultato è:

@x
-----------
88698

Se ora stai vivendo un momento WTF (dopo tutto number è la chiave primaria in cluster!), Il trucco si chiama scansione dell'ordine di allocazione e non ho inserito @j*1000+@iper caso ... Potresti anche avventurarti in un'ipotesi e dire che il risultato è perché parallelismo e che a volte può essere la risposta corretta.

Ci sono molti troll sotto questo bridge e ne ho menzionati alcuni in On SQL Server, il cortocircuito dell'operatore booleano e le funzioni T-SQL non implicano un certo ordine di esecuzione


55

Ho trovato una tabella dei numeri abbastanza utile in una varietà di situazioni.

Alla Perché dovrei considerare l'utilizzo di una tabella di numeri ausiliario? , scritto nel 2004, mostro alcuni esempi:

  • Analizzare una stringa
  • Trovare lacune di identità
  • Generazione di intervalli di date (ad esempio popolamento di una tabella del calendario, che può anche essere preziosa)
  • Generare fasce orarie
  • Generazione di intervalli IP

A Cattive abitudini da calciare: usando i loop per popolare tabelle di grandi dimensioni , mostro come una tabella di numeri può essere usata per fare un breve lavoro di inserimento di molte righe (al contrario dell'approccio istintivo di usare un ciclo while).

In Elaborazione di un elenco di numeri interi: il mio approccio e altro sulla suddivisione degli elenchi: delimitatori personalizzati, prevenzione dei duplicati e mantenimento dell'ordine , mostro come utilizzare una tabella numerica per dividere una stringa (ad esempio un insieme di valori separati da virgola) e fornire prestazioni confronti tra questo e altri metodi. Ulteriori informazioni sulla divisione e altre operazioni di gestione delle stringhe:

E in The SQL Server Numbers Table, Explained - Part 1 , fornisco alcune informazioni sul concetto e ho in serbo post futuri per dettagliare applicazioni specifiche.

Ci sono molti altri usi, quelli sono solo alcuni che mi sono emersi abbastanza da scriverne.

E come @gbn, ho alcune risposte sullo overflow dello stack e su questo sito che usano anche una tabella numerica.

Infine, ho una serie di post sul blog sulla generazione di set senza loop, che in parte mostrano il vantaggio in termini di prestazioni dell'uso di una tabella numerica rispetto alla maggior parte degli altri metodi (a parte il bizzarro outlier di Remus):


26

Ecco un ottimo esempio che ho usato di recente da Adam Machanic:

CREATE FUNCTION dbo.GetSubstringCount
(
    @InputString TEXT, 
    @SubString VARCHAR(200),
    @NoisePattern VARCHAR(20)
)
RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
    RETURN 
    (
        SELECT COUNT(*)
        FROM dbo.Numbers N
        WHERE
            SUBSTRING(@InputString, N.Number, LEN(@SubString)) = @SubString
            AND PATINDEX(@NoisePattern, SUBSTRING(@InputString, N.Number + LEN(@SubString), 1)) = 0
            AND 0 = 
                CASE 
                    WHEN @NoisePattern = '' THEN 0
                    ELSE PATINDEX(@NoisePattern, SUBSTRING(@InputString, N.Number - 1, 1))
                END
    )
END

Ho usato qualcos'altro simile a CTEper trovare un'istanza specifica di sottostringa (ovvero "Trova la terza pipe in questa stringa") per lavorare con dati delimitati correlati:

declare @TargetStr varchar(8000), 
@SearchedStr varchar(8000), 
@Occurrence int
set @TargetStr='a'
set @SearchedStr='abbabba'
set @Occurrence=3;

WITH Occurrences AS (
SELECT Number,
       ROW_NUMBER() OVER(ORDER BY Number) AS Occurrence
FROM master.dbo.spt_values
WHERE Number BETWEEN 1 AND LEN(@SearchedStr) AND type='P'
  AND SUBSTRING(@SearchedStr,Number,LEN(@TargetStr))=@TargetStr)
SELECT Number
FROM Occurrences
WHERE Occurrence=@Occurrence

Se non si dispone di una tabella numerica, l'alternativa è utilizzare un ciclo di qualche tipo. Fondamentalmente, una tabella numerica consente di eseguire iterazioni basate su set, senza cursori o loop.


5
E l'avvertimento obbligatorio sul pericolo in agguato di manipolazione di stringhe in TVF in linea: le funzioni T-SQL non implicano un certo ordine di esecuzione
Remus Rusanu,

12

Vorrei utilizzare una tabella numerica ogni volta che ho bisogno di un equivalente SQL di Enumerable.Range. Ad esempio, l'ho appena usato in una risposta su questo sito: calcolo del numero di permutazioni

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.