sql >> Base de Datos >  >> RDS >> Database

Generar un conjunto o secuencia sin loops – parte 2

En mi publicación anterior, hablé sobre formas de generar una secuencia de números contiguos del 1 al 1000. Ahora me gustaría hablar sobre los siguientes niveles de escala:conjuntos generadores de 50 000 y 1 000 000 de números.

Generar un conjunto de 50.000 números

Cuando comencé esta serie, tenía mucha curiosidad sobre cómo los diferentes enfoques se escalarían a conjuntos de números más grandes. En el extremo inferior, estaba un poco consternado al descubrir que mi enfoque favorito:usar sys.all_objects – no era el método más eficiente. Pero, ¿cómo se escalarían estas diferentes técnicas a 50 000 filas?

    Tabla de números

    Dado que ya hemos creado una tabla de Números con 1 000 000 de filas, esta consulta sigue siendo prácticamente idéntica:

    SELECT TOP (50000) n FROM dbo.Numbers ORDER BY n;

    Plano:

    valores_spt

    Dado que solo hay ~2500 filas en spt_values , necesitamos ser un poco más creativos si queremos usarlo como fuente de nuestro generador de conjuntos. Una forma de simular una tabla más grande es CROSS JOIN ella contra sí misma. Si lo hiciéramos sin procesar, terminaríamos con ~2500 filas al cuadrado (más de 6 millones). Al necesitar solo 50,000 filas, necesitamos alrededor de 224 filas al cuadrado. Entonces podemos hacer esto:

    ;WITH x AS 
    (
      SELECT TOP (224) number FROM [master]..spt_values
    )
    SELECT TOP (50000) n = ROW_NUMBER() OVER (ORDER BY x.number) 
    FROM x CROSS JOIN x AS y
    ORDER BY n;

    Tenga en cuenta que esto es equivalente a, pero más conciso que, esta variación:

    SELECT TOP (50000) n = ROW_NUMBER() OVER (ORDER BY x.number) 
    FROM 
    (SELECT TOP (224) number FROM [master]..spt_values) AS x
    CROSS JOIN
    (SELECT TOP (224) number FROM [master]..spt_values) AS y
    ORDER BY n;

    En ambos casos, el plan se ve así:

    sys.todos_los_objetos

    Me gusta spt_values , sys.all_objects no satisface por sí solo nuestro requisito de 50 000 filas, por lo que tendremos que realizar una CROSS JOIN similar .

    ;;WITH x AS 
    (
      SELECT TOP (224) [object_id] FROM sys.all_objects
    )
    SELECT TOP (50000) n = ROW_NUMBER() OVER (ORDER BY x.[object_id]) 
    FROM x CROSS JOIN x AS y 
    ORDER BY n;

    Plano:

    CTE apilados

    Solo necesitamos hacer un ajuste menor a nuestros CTE apilados para obtener exactamente 50 000 filas:

    ;WITH e1(n) AS
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    ), -- 10
    e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
    e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
    e4(n) AS (SELECT 1 FROM e3 CROSS JOIN (SELECT TOP 5 n FROM e1) AS b)  -- 5*10000
      SELECT n = ROW_NUMBER() OVER (ORDER BY n) FROM e4 ORDER BY n;

    Plano:

    CTEs recursivos

    Se requiere un cambio aún menos sustancial para obtener 50,000 filas de nuestro CTE recursivo:cambie el WHERE cláusula a 50,000 y cambie el MAXRECURSION opción a cero.

    ;WITH n(n) AS
    (
        SELECT 1
        UNION ALL
        SELECT n+1 FROM n WHERE n < 50000
    )
    SELECT n FROM n ORDER BY n
    OPTION (MAXRECURSION 0);

    Plano:

    En este caso, hay un ícono de advertencia en la ordenación; resulta que, en mi sistema, la ordenación necesitaba pasar a tempdb. Es posible que no vea un derrame en su sistema, pero esto debería ser una advertencia sobre los recursos necesarios para esta técnica.

    Rendimiento

    Al igual que con el último conjunto de pruebas, compararemos cada técnica, incluida la tabla Numbers con una caché fría y caliente, y tanto comprimida como sin comprimir:


    Tiempo de ejecución, en milisegundos, para generar 50 000 números contiguos

    Para obtener una mejor visualización, eliminemos el CTE recursivo, que fue un perro total en esta prueba y que distorsiona los resultados:


    Tiempo de ejecución, en milisegundos, para generar 50 000 números contiguos (excluyendo recursivos CET)

    Con 1000 filas, la diferencia entre comprimido y sin comprimir era mínima, ya que la consulta solo necesitaba leer 8 y 9 páginas respectivamente. Con 50 000 filas, la brecha se amplía un poco:74 páginas frente a 113. Sin embargo, el costo total de descomprimir los datos parece superar los ahorros en E/S. Entonces, con 50 000 filas, una tabla de números sin comprimir parece ser el método más eficiente para derivar un conjunto contiguo, aunque, hay que admitirlo, la ventaja es marginal.

Generando un conjunto de 1,000,000 de números

Si bien no puedo imaginar muchos casos de uso en los que necesite un conjunto contiguo de números tan grandes, quería incluirlo para completarlo y porque hice algunas observaciones interesantes a esta escala.

    Tabla de números

    No hay sorpresas aquí, nuestra consulta ahora es:

    SELECT TOP 1000000 n FROM dbo.Numbers ORDER BY n;

    El TOP no es estrictamente necesario, pero eso es solo porque sabemos que nuestra tabla Numbers y nuestra salida deseada tienen el mismo número de filas. El plan sigue siendo bastante similar a las pruebas anteriores:

    valores_spt

    Para obtener un CROSS JOIN eso produce 1,000,000 filas, necesitamos tomar 1,000 filas al cuadrado:

    ;WITH x AS 
    (
      SELECT TOP (1000) number FROM [master]..spt_values
    )
    SELECT n = ROW_NUMBER() OVER (ORDER BY x.number) 
    FROM x CROSS JOIN x AS y ORDER BY n;
    >

    Plano:

    sys.todos_los_objetos

    De nuevo, necesitamos el producto vectorial de 1000 filas:

    ;WITH x AS 
    (
      SELECT TOP (1000) [object_id] FROM sys.all_objects
    )
    SELECT n = ROW_NUMBER() OVER (ORDER BY x.[object_id]) 
    FROM x CROSS JOIN x AS y ORDER BY n;

    Plano:

    CTE apilados

    Para el CTE apilado, solo necesitamos una combinación ligeramente diferente de CROSS JOIN s para llegar a 1 000 000 de filas:

    ;WITH e1(n) AS
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    ), -- 10
    e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
    e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2 AS b), -- 10*100
    e4(n) AS (SELECT 1 FROM e3 CROSS JOIN e3 AS b)  -- 1000*1000
      SELECT n = ROW_NUMBER() OVER (ORDER BY n) FROM e4 ORDER BY n;

    Plano:

    Con este tamaño de fila, puede ver que la solución CTE apilada va en paralelo. Así que también ejecuté una versión con MAXDOP 1 para obtener una forma de plan similar a la anterior y para ver si el paralelismo realmente ayuda:

    CTE recursivo

    El CTE recursivo nuevamente tiene solo un cambio menor; solo el WHERE la cláusula debe cambiar:

    ;WITH n(n) AS
    (
        SELECT 1
        UNION ALL
        SELECT n+1 FROM n WHERE n < 1000000
    )
    SELECT n FROM n ORDER BY n
    OPTION (MAXRECURSION 0);

    Plano:

    Rendimiento

    Una vez más vemos que el rendimiento del CTE recursivo es abismal:


    Tiempo de ejecución, en milisegundos, para generar 1 000 000 de números contiguos

    Al eliminar ese valor atípico del gráfico, obtenemos una mejor imagen del rendimiento:


    Tiempo de ejecución, en milisegundos, para generar 1 000 000 números contiguos (excluyendo recursivos CET)

    Si bien nuevamente vemos la tabla Numbers sin comprimir (al menos con un caché tibio) como la ganadora, la diferencia, incluso a esta escala, no es tan notable.

Continuará...

Ahora que hemos explorado a fondo un puñado de enfoques para generar una secuencia de números, pasaremos a las fechas. En la publicación final de esta serie, veremos la construcción de un rango de fechas como un conjunto, incluido el uso de una tabla de calendario y algunos casos de uso en los que esto puede ser útil.

[ Parte 1 | Parte 2 | Parte 3 ]

Apéndice:Recuento de filas

Es posible que no esté intentando generar un número exacto de filas; en cambio, es posible que solo desee una forma sencilla de generar muchas filas. La siguiente es una lista de combinaciones de vistas de catálogo que le darán varios recuentos de filas si simplemente SELECT sin un WHERE cláusula. Tenga en cuenta que estos números dependerán de si se encuentra en un RTM o en un Service Pack (ya que algunos objetos del sistema se agregan o modifican) y también si tiene una base de datos vacía.

Fuente Número de filas
Servidor SQL 2008 R2 Servidor SQL 2012 Servidor SQL 2014
maestro..spt_valores

2508

2515 2519
maestro..valores_spt CROSS JOIN maestro..valores_spt

6,290,064

6,325,225 6,345,361
sys.todos_los_objetos

1990

2089 2165
sys.todas_las_columnas

5157

7276 8560
sys.all_objects CROSS JOIN sys.all_objects

3.960.100

4.363.921 4.687.225
sys.all_objects CROSS JOIN sys.all_columns

10,262,430

15,199,564 18,532,400
sys.all_columns CROSS JOIN sys.all_columns

26 594 649

52,940,176 73,273,600

Tabla 1:Recuentos de filas para varias consultas de vista de catálogo