¿Volver a buscar rango en índice compuesto anulable?


14

Para el siguiente esquema y datos de ejemplo

CREATE TABLE T
  (
     A INT NULL,
     B INT NOT NULL IDENTITY,
     C CHAR(8000) NULL,
     UNIQUE CLUSTERED (A, B)
  )

INSERT INTO T
            (A)
SELECT NULLIF(( ( ROW_NUMBER() OVER (ORDER BY @@SPID) - 1 ) / 1003 ), 0)
FROM   master..spt_values 

Una aplicación está procesando las filas de esta tabla en orden de índice agrupado en 1,000 fragmentos de fila.

Las primeras 1000 filas se recuperan de la siguiente consulta.

SELECT TOP 1000 *
FROM   T
ORDER  BY A, B 

La fila final de ese conjunto está debajo

+------+------+
|  A   |  B   |
+------+------+
| NULL | 1000 |
+------+------+

¿Hay alguna forma de escribir una consulta que solo busque en esa clave de índice compuesta y luego la siga para recuperar el siguiente fragmento de 1000 filas?

/*Pseudo Syntax*/
SELECT TOP 1000 *
FROM   T
WHERE (A, B) is_ordered_after (@A, @B)
ORDER  BY A, B 

El número más bajo de lecturas que he logrado hasta ahora es 1020, pero la consulta parece demasiado complicada. ¿Existe una forma más simple de igual o mejor eficiencia? Tal vez uno que logra hacer todo en un rango de búsqueda?

DECLARE @A INT = NULL, @B INT = 1000

;WITH UnProcessed
     AS (SELECT *
         FROM   T
         WHERE  ( EXISTS(SELECT A
                         INTERSECT
                         SELECT @A)
                  AND B > @B )
         UNION ALL
         SELECT *
         FROM   T
         WHERE @A IS NULL AND A IS NOT NULL
         UNION ALL
         SELECT *
         FROM   T
         WHERE A > @A        
         )
SELECT TOP 1000 *
FROM   UnProcessed
ORDER  BY A,
          B 

ingrese la descripción de la imagen aquí


Fwiw: Si la columna Aestá hecha NOT NULLy un valor centinela de -1su lugar se usa el plan de ejecución equivalente sin duda parece más sencillo

ingrese la descripción de la imagen aquí

Pero el operador de búsqueda única en el plan todavía realiza dos búsquedas en lugar de colapsarlo en un solo rango contiguo y las lecturas lógicas son muy similares, así que sospecho que tal vez esto sea tan bueno como sea posible.


Mi error. Olvidé que los NULLvalores son siempre los primeros. (asumió lo contrario.) Condición corregida en Fiddle
ypercubeᵀᴹ

Sí, Oracle es diferente, creo.
Martin Smith


@ypercube: SQL Server solo ofrece un análisis ordenado para eso, por lo que, por lo tanto, vuelve a leer todas las filas ya procesadas por la aplicación (lecturas lógicas 2015). No busca la primera clave de(NULL, 1000 )
Martin Smith

Con 2 condiciones diferentes, sobre si @Aes nulo o no, parece que no hace un escaneo. Pero no puedo entender si los planes son mejores que su consulta. Fiddle-2
ypercubeᵀᴹ

Respuestas:


21

¿Hay alguna forma de escribir una consulta que solo busque en esa clave de índice compuesta y luego la siga para recuperar el siguiente fragmento de 1000 filas?

Una de mis soluciones favoritas es usar un APIcursor:

SET NOCOUNT ON;
SET STATISTICS IO ON;

DECLARE 
    @cur integer,
    -- FAST_FORWARD, AUTO_FETCH, AUTO_CLOSE, CHECK_ACCEPTED_TYPES, FAST_FORWARD_ACCEPTABLE
    @scrollopt integer = 16 | 8192 | 16384 | 32768 | 1048576,
    -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE
    @ccopt integer = 1 | 32768 | 65536, 
    @rowcount integer = 1000,
    @rc integer;

-- Open the cursor and return (up to) the first 1000 rows
EXECUTE @rc = sys.sp_cursoropen
    @cur OUTPUT,
    N'
    SELECT A, B, C
    FROM T
    ORDER BY A, B;
    ',
    @scrollopt OUTPUT,
    @ccopt OUTPUT,
    @rowcount OUTPUT;

IF @rc <> 16 -- FastForward cursor automatically closed
BEGIN
    -- Name the cursor so we can use CURSOR_STATUS
    EXECUTE sys.sp_cursoroption
        @cur, 
        2, 
        'MyCursorName';

    -- Until the cursor auto-closes
    WHILE CURSOR_STATUS('global', 'MyCursorName') = 1
    BEGIN
        EXECUTE sys.sp_cursorfetch
            @cur,
            2,
            0,
            1000;
    END;
END;

SET STATISTICS IO OFF;

La estrategia general es un escaneo único que recuerda su posición entre llamadas. Usar un APIcursor significa que podemos devolver un bloque de filas en lugar de uno a la vez, como sería el caso con un T-SQLcursor:

Planes de ejecucion

El STATISTICS IOresultado es:

Table 'T'. Scan count 1, logical reads 1011, physical reads 0, read-ahead reads 0
Table 'T'. Scan count 1, logical reads 1001, physical reads 0, read-ahead reads 0
Table 'T'. Scan count 1, logical reads 516, physical reads 0, read-ahead reads 0
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.