Pourquoi les tableaux de nombres sont-ils «précieux»?

112

Notre expert en bases de données sur les résidents nous dit que les tableaux de chiffres sont inestimables . Je ne comprends pas trop pourquoi. Voici un tableau de chiffres:

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

Selon le blog, la justification est la suivante:

Les tableaux de chiffres sont vraiment précieux. Je les utilise tout le temps pour manipuler des chaînes, simuler des fonctions de fenêtre, remplir des tables de test avec beaucoup de données, éliminer la logique du curseur et de nombreuses autres tâches qui seraient incroyablement difficiles sans elles.

Mais je ne comprends pas exactement ce que sont ces utilisations - pouvez-vous fournir des exemples convaincants et précis de "tableaux de chiffres" vous épargnant une tonne de travail dans SQL Server - et pourquoi nous devrions les avoir?

Jeff Atwood
la source
3
De nombreux cas d'utilisation d'un tableau de nombres peuvent également être satisfaits par un CTE récursif qui génère les nombres dont vous avez besoin à la volée. Cependant, il y a une pénalité de performance ainsi que d'autres limitations de l'approche CTE.
Nick Chammas
4
@ Nick: Je dirais qu'une table de nombres à la volée basée sur un CTE par rapport à une table physique n'est qu'un détail de la mise en œuvre de la façon dont vous générez la table de nombres. Pomme de terre contre ...
Remus Rusanu
1
@Remus - Yup. Je voulais juste souligner cette alternative à Jeff.
Nick Chammas
2
J'ai une douzaine de réponses en utilisant une table de nombres sur SO stackoverflow.com/search?q=user%3A27535+%2B%22numbers+table%22 .
gbn

Réponses:

82

J'ai vu de nombreuses utilisations lorsque vous devez projeter des «données manquantes». Par exemple. vous avez une série chronologique (un journal d'accès par exemple) et vous souhaitez afficher le nombre de visites par jour au cours des 30 derniers jours (pensez au tableau de bord d'analyse). Si vous faites un, select count(...) from ... group by dayvous obtiendrez le nombre pour chaque jour, mais le résultat n'aura qu'une ligne pour chaque jour où vous avez eu au moins un accès. Par contre, si vous projetez d’abord une table de jours à partir de votre table de chiffres ( select dateadd(day, -number, today) as day from numbers) et que vous quittez ensuite la liste des comptes eu aucun accès. Ceci n'est qu'un exemple. Bien sûr, on peut faire valoir que la couche de présentation de votre tableau de bord pourrait gérer les jours manquants et simplement afficher un 0, mais certains outils (par exemple, SSRS) ne pourront tout simplement pas gérer cela.

D'autres exemples que j'ai vus utilisaient des astuces similaires sur les séries chronologiques (date / heure + / - nombre) pour effectuer toutes sortes de calculs de fenêtre. En général, lorsque vous utilisez une boucle for avec un nombre bien connu d'itérations dans un langage impératif, la nature déclarative et définie de SQL peut utiliser une astuce basée sur une table de nombres.

BTW, je ressens le besoin de rappeler le fait que, même si utiliser un tableau de nombres, cela ressemble à une exécution procédurale impérative, ne tombez pas dans l'erreur de supposer qu'il est impératif. Laissez-moi vous donner un exemple:

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

Ce programme produira 999999, ce qui est pratiquement garanti.

Essayons la même chose dans SQL Server, en utilisant une table numérique. Commencez par créer une table de 1 000 000 nombres:

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

Maintenant, faisons la boucle "for":

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

Le résultat est:

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

Si vous avez maintenant un moment WTF (après tout, number c'est la clé primaire en cluster!), L'astuce s'appelle le balayage d'ordre d'attribution et je ne l'ai pas insérée @j*1000+@ipar accident ... Vous pourriez aussi deviner et dire que le résultat est parce que parallélisme et que parfois peut être la bonne réponse.

Il y a beaucoup de trolls sous ce pont et j'en ai mentionné quelques-uns dans On SQL Server, un court-circuit d'opérateur booléen et des fonctions T-SQL n'impliquant pas un certain ordre d'exécution

Remus Rusanu
la source
55

J'ai trouvé un tableau de chiffres assez utile dans diverses situations.

A Pourquoi devrais - je envisager d' utiliser une table de numéros auxiliaires? , écrit en 2004, je montre quelques exemples:

  • Analyser une chaîne
  • Trouver des lacunes d'identité
  • Générer des plages de dates (par exemple, remplir une table de calendrier, ce qui peut aussi être inestimable)
  • Générer des tranches de temps
  • Générer des plages IP

Dans Bad habitudes to kick: en utilisant des boucles pour remplir de grandes tables , je montre comment utiliser une table de nombres pour insérer rapidement un grand nombre de lignes (par opposition à l’approche réflexe qui consiste à utiliser une boucle while).

Dans Traitement d’une liste d’entiers: mon approche et Autres explications sur le fractionnement des listes: délimiteurs personnalisés, prévention des doublons et maintien de l’ordre , je montre comment utiliser un tableau de nombres pour fractionner une chaîne (par exemple, un ensemble de valeurs séparées par des virgules) et fournir des performances. des comparaisons entre cette méthode et d’autres méthodes. Plus d'informations sur le fractionnement et autres manipulations de chaîne:

Et dans The SQL Server Numbers Table, Explained - Part 1 , je donne quelques informations de base sur le concept et les futures publications en magasin détaillent des applications spécifiques.

Il y a beaucoup d'autres utilisations, ce ne sont que quelques-unes qui m'ont suffisamment marqué pour écrire à leur sujet.

Et comme @gbn, j'ai quelques réponses sur le dépassement de capacité de la pile et sur ce site qui utilisent également un tableau de nombres.

Enfin, j'ai une série de billets de blog sur les groupes générateurs sans boucle, ce qui montre en partie l'avantage de l'utilisation d'un tableau de nombres par rapport à la plupart des autres méthodes (mis à part l'extrême bizarre de Remus):

Aaron Bertrand
la source
26

Voici un excellent exemple d' Adam Machanic que j'ai utilisé récemment :

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

J'ai utilisé quelque chose de similaire avec a CTEpour trouver une instance spécifique de sous-chaîne ("Trouvez le 3ème canal de cette chaîne") pour travailler avec des données délimitées corrélées:

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

Si vous n'avez pas de tableau de nombres, l'alternative consiste à utiliser une boucle quelconque. Fondamentalement, un tableau de nombres vous permet d'effectuer une itération basée sur un ensemble, sans curseurs ni boucles.

JNK
la source
5
Et l'avertissement obligatoire sur le danger imminent de manipulation de chaînes dans les fichiers TVF intégrés: les fonctions T-SQL n'impliquent pas un certain ordre d'exécution
Remus Rusanu
12

Je voudrais utiliser une table de nombres chaque fois que j'ai besoin d'un équivalent SQL d'Enumerable.Range. Par exemple, je viens de l’utiliser dans une réponse sur ce site: calcul du nombre de permutations

AK
la source