¿Por qué las tablas de números son "invaluables"?


112

Nuestro experto residente en bases de datos nos dice que las tablas de números son invaluables . No entiendo bien por qué. Aquí hay una tabla de números:

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

Según la publicación del blog, la justificación dada es

Las tablas de números son realmente invaluables. Los uso todo el tiempo para la manipulación de cadenas, simular funciones de ventana, llenar tablas de prueba con muchos datos, eliminar la lógica del cursor y muchas otras tareas que serían increíblemente difíciles sin ellas.

Pero no entiendo exactamente cuáles son esos usos, ¿puede proporcionar algunos ejemplos convincentes y específicos de dónde una "tabla de números" le ahorra una tonelada de trabajo en SQL Server, y por qué deberíamos tenerlos?


3
Muchos casos de uso para una tabla de números pueden satisfacerse igualmente con un CTE recursivo que genera los números que necesita sobre la marcha. Sin embargo, hay una penalización de rendimiento, así como algunas otras limitaciones para el enfoque CTE.
Nick Chammas

44
@Nick: Diría que una tabla de números basada en CTE sobre la marcha versus una tabla física es solo un detalle de implementación de cómo se genera la tabla de números. Patata vs. Patata ...
Remus Rusanu

1
@Remus - Sí. Solo quería señalar esta alternativa a Jeff.
Nick Chammas

2
Tengo una docena de respuestas usando una tabla de números en SO stackoverflow.com/search?q=user%3A27535+%2B%22numbers+table%22 .
gbn

Respuestas:


82

He visto muchos usos cuando necesitas proyectar 'datos faltantes'. P.ej. tiene una serie temporal (un registro de acceso, por ejemplo) y desea mostrar la cantidad de visitas por día durante los últimos 30 días (piense en el panel de análisis). Si lo haces select count(...) from ... group by day, obtendrás el recuento de cada día, pero el resultado solo tendrá una fila por cada día en que realmente tengas al menos un acceso. Por otro lado, si primero proyecta una tabla de días de su tabla de números ( select dateadd(day, -number, today) as day from numbers) y luego se une a los recuentos (o aplicación externa, lo que desee), obtendrá un resultado que tiene 0 para contar los días que No tenía acceso. Esto es sólo un ejemplo. Por supuesto, uno puede argumentar que la capa de presentación de su tablero podría manejar los días que faltan y simplemente mostrar un 0, pero algunas herramientas (por ejemplo, SSRS) simplemente no podrán manejar esto.

Otros ejemplos que he visto utilizan trucos de series de tiempo similares (fecha / hora +/- número) para hacer todo tipo de cálculos de ventanas. En general, siempre que en un lenguaje imperativo use un bucle for con un número conocido de iteraciones, la naturaleza declarativa y establecida de SQL puede usar un truco basado en una tabla de números.

Por cierto, siento la necesidad de mencionar el hecho de que, aunque usar una tabla de números se siente como una ejecución procesal imperativa, no caiga en la falacia de suponer que es imprescindible. Déjame dar un ejemplo:

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

Este programa generará 999999, eso está prácticamente garantizado.

Probemos lo mismo en SQL Server, usando una tabla de números. Primero cree una tabla de 1,000,000 de números:

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

Ahora hagamos el 'bucle for':

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

El resultado es:

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

Si ahora está teniendo un momento WTF (¡después de todo number es la clave primaria agrupada!), El truco se llama exploración de orden de asignación y no lo inserté @j*1000+@ipor accidente ... También podría aventurarse a adivinar y decir que el resultado es porque paralelismo y que a veces puede ser la respuesta correcta.

Hay muchos trolls bajo este puente y mencioné algunos en las funciones de cortocircuito del operador booleano en el servidor SQL y las funciones T-SQL no implican un cierto orden de ejecución


55

He encontrado una tabla de números bastante útil en una variedad de situaciones.

¿ Por qué debería considerar usar una tabla de números auxiliares? , escrito en 2004, muestro algunos ejemplos:

  • Analizando una cuerda
  • Encontrar brechas de identidad
  • Generar rangos de fechas (por ejemplo, llenar una tabla de calendario, que también puede ser invaluable)
  • Generando segmentos de tiempo
  • Generando rangos de IP

Con los malos hábitos para patear: usando bucles para poblar tablas grandes , muestro cómo se puede usar una tabla de números para hacer un trabajo corto de insertar muchas filas (en oposición al enfoque instintivo de usar un bucle while).

En Procesar una lista de enteros: mi enfoque y más sobre la división de listas: delimitadores personalizados, prevención de duplicados y mantenimiento del orden , muestro cómo usar una tabla de números para dividir una cadena (por ejemplo, un conjunto de valores separados por comas) y proporcionar rendimiento comparaciones entre este y otros métodos. Más información sobre división y otro manejo de cadenas:

Y en la Tabla de Números de SQL Server, Explicada - Parte 1 , doy algunos antecedentes sobre el concepto y tengo futuras publicaciones en la tienda para detallar aplicaciones específicas.

Hay muchos otros usos, esos son solo algunos que me han destacado lo suficiente como para escribir sobre ellos.

Y como @gbn, tengo algunas respuestas sobre el desbordamiento de pila y en este sitio que también usan una tabla de números.

Finalmente, tengo una serie de publicaciones de blog sobre la generación de conjuntos sin bucles, que en parte muestran la ventaja de rendimiento de usar una tabla de números en comparación con la mayoría de los otros métodos (aparte del peculiar atípico de Remus):


26

Aquí hay un gran ejemplo que usé recientemente de 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

Utilicé algo más similar con a CTEpara encontrar una instancia específica de subcadena (es decir, "Buscar la tercera tubería en esta cadena") para trabajar con datos delimitados correlacionados:

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 no tiene una tabla de números, la alternativa es usar un ciclo de algún tipo. Básicamente, una tabla de números le permite realizar iteraciones basadas en conjuntos, sin cursores ni bucles.


55
Y la advertencia obligatoria sobre el peligro al acecho de manipular cadenas en TVFs en línea: las funciones T-SQL no implican un cierto orden de ejecución
Remus Rusanu

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.