Cerchi di ricongiungimento cercano un indice composito nullable?


14

Per il seguente schema e dati di esempio

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 

Un'applicazione sta elaborando le righe da questa tabella in ordine di indice cluster in blocchi di 1.000 righe.

Le prime 1.000 righe vengono recuperate dalla query seguente.

SELECT TOP 1000 *
FROM   T
ORDER  BY A, B 

L'ultima riga di quel set è sotto

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

Esiste un modo per scrivere una query che cerca solo quella chiave di indice composita e quindi la segue per recuperare il blocco successivo di 1000 righe?

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

Il numero più basso di letture che sono riuscito a ottenere finora è 1020 ma la query sembra troppo contorta. Esiste un modo più semplice di uguale o migliore efficienza? Forse uno che riesce a fare tutto in una gamma cerca?

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 

inserisci qui la descrizione dell'immagine


FWIW: se Aviene creata la colonna NOT NULLe -1viene utilizzato un valore sentinella di invece il piano di esecuzione equivalente sembra sicuramente più semplice

inserisci qui la descrizione dell'immagine

Ma l'operatore di ricerca singola nel piano esegue ancora due ricerche invece di comprimerlo in un singolo intervallo contiguo e le letture logiche sono più o meno le stesse, quindi sospetto che forse sia abbastanza buono come sarà?


Errore mio. Ho dimenticato che i NULLvalori sono sempre i primi. (assunto il contrario). Condizione corretta a Fiddle
ypercubeᵀᴹ

Sì, Oracle è diverso, credo.
Martin Smith,


@ypercube - Sfortunatamente SQL Server esegue una scansione ordinata per questo, quindi rilegge tutte le righe già elaborate dall'applicazione (letture logiche 2015). Non cerca la prima chiave di(NULL, 1000 )
Martin Smith,

Con 2 condizioni diverse, indipendentemente dal fatto che @Asia nullo o meno, sembra che non esegua una scansione. Ma non riesco a capire se i piani sono migliori della tua domanda. Fiddle-2
ypercubeᵀᴹ

Risposte:


21

Esiste un modo per scrivere una query che cerca solo quella chiave di indice composita e quindi la segue per recuperare il blocco successivo di 1000 righe?

Una mia soluzione preferita è quella di utilizzare un API cursore:

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 strategia complessiva è una singola scansione che ricorda la sua posizione tra le chiamate. L'uso di un APIcursore significa che possiamo restituire un blocco di righe anziché uno alla volta, come nel caso di un T-SQLcursore:

Piani di esecuzione

L' STATISTICS IOoutput è:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.