it-swarm.it

Perché le tabelle dei numeri sono "preziose"?

Il nostro residente esperto di database 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?

112
Jeff Atwood

Ho visto molti usi quando è necessario proiettare "dati mancanti". Per esempio. hai una serie storica (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 esegui una select count(...) from ... group by day otterrai 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 esteriore, qualunque cosa tu voglia), otterrai un risultato che ha 0 per conteggio per i giorni in cui non hai avuto 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 numerica feel come l'esecuzione procedurale imperativa, non cade nell'errore di assumerlo lo è 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*[email protected]);
        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 ordine allocazione e non ho inserito @j*[email protected] per caso ... Potresti anche avventurarti in un'ipotesi e dire che il risultato è perché parallelismo e che a volte può essere la risposta corretta.

Esistono molti troll sotto questo ponte e ne ho menzionato alcuni in Su cortocircuito operatore booleano di SQL Server e Le funzioni T-SQL non implicano un certo ordine di esecuzione

82
Remus Rusanu

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

A Perché dovrei considerare l'utilizzo di una tabella di numeri ausiliari? , scritta 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: usare 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 dell'uso un ciclo while).

A Elaborazione di un elenco di numeri interi: il mio approccio e lteriori informazioni sulla suddivisione degli elenchi: delimitatori personalizzati, prevenzione dei duplicati e mantenimento dell'ordine , mostro come utilizzare una tabella numerica per dividere un stringa (ad esempio un insieme di valori separati da virgola) e fornisce confronti di prestazioni 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):

56
Aaron Bertrand

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 CTE per 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))[email protected])
SELECT Number
FROM Occurrences
WHERE [email protected]

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.

26
JNK

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

12
A-K