Quali sono i diversi modi per sostituire ISNULL () in una clausola WHERE che utilizza solo valori letterali?


55

Di cosa non si tratta:

Questa non è una domanda sulle query generali che accettano l'input dell'utente o utilizzano variabili.

Si tratta esclusivamente di query in cui ISNULL()viene utilizzato nella WHEREclausola per sostituire i NULLvalori con un valore canarino per il confronto con un predicato e diversi modi per riscrivere tali query in modo che siano SARGable in SQL Server.

Perché non ti siedi lì?

La nostra query di esempio è confrontata con una copia locale del database Stack Overflow su SQL Server 2016 e cerca utenti con NULLun'età o un'età <18 anni.

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

Il piano di query mostra una scansione di un indice non cluster abbastanza ponderato.

Noccioline

L'operatore di scansione mostra (grazie alle aggiunte all'effettivo XML del piano di esecuzione nelle versioni più recenti di SQL Server) che leggiamo ogni riga puzzolente.

Noccioline

Complessivamente, eseguiamo 9157 letture e utilizziamo circa mezzo secondo del tempo della CPU:

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 485 ms,  elapsed time = 483 ms.

La domanda: quali sono i modi per riscrivere questa query per renderla più efficiente e forse anche SARGable?

Sentiti libero di offrire altri suggerimenti. Non penso che la mia risposta sia necessariamente la risposta, e ci sono abbastanza persone intelligenti là fuori per trovare alternative che potrebbero essere migliori.

Se vuoi giocare sul tuo computer, vai qui per scaricare il database SO .

Grazie!

Risposte:


57

Sezione risposta

Esistono vari modi per riscriverlo usando diversi costrutti T-SQL. Esamineremo i pro e i contro e faremo un confronto generale di seguito.

Primo : utilizzoOR

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

L'utilizzo ORci fornisce un piano di ricerca più efficiente, che legge il numero esatto di righe di cui abbiamo bisogno, tuttavia aggiunge ciò che il mondo tecnico chiama a whole mess of malarkeyal piano di query.

Noccioline

Si noti inoltre che la ricerca viene eseguita due volte qui, il che dovrebbe essere più evidente dall'operatore grafico:

Noccioline

Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 469 ms,  elapsed time = 473 ms.

Secondo : utilizzare anche le tabelle derivate con UNION ALL La nostra query può essere riscritto in questo modo

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Ciò produce lo stesso tipo di piano, con molto meno malarkey e un grado più evidente di onestà su quante volte l'indice è stato cercato (cercato?).

Noccioline

Fa la stessa quantità di letture (8233) della ORquery, ma riduce circa 100 ms di tempo libero della CPU.

CPU time = 313 ms,  elapsed time = 315 ms.

Tuttavia, devi stare molto attento qui, perché se questo piano tenta di andare in parallelo, le due COUNToperazioni separate saranno serializzate, perché ognuna è considerata un aggregato scalare globale. Se forziamo un piano parallelo usando Trace Flag 8649, il problema diventa ovvio.

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);

Noccioline

Questo può essere evitato modificando leggermente la nostra query.

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Ora entrambi i nodi che eseguono una ricerca sono completamente parallelizzati fino a quando non colpiamo l'operatore di concatenazione.

Noccioline

Per quello che vale, la versione completamente parallela ha dei buoni benefici. Al costo di circa 100 letture in più e circa 90 ms di tempo di CPU aggiuntivo, il tempo trascorso si riduce a 93 ms.

Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 500 ms,  elapsed time = 93 ms.

Che dire di CROSS APPLY? Nessuna risposta è completa senza la magia di CROSS APPLY!

Sfortunatamente, incontriamo più problemi con COUNT.

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Questo piano è orribile. Questo è il tipo di piano che finisci quando ti presenti per l'ultima volta al giorno di San Patrizio. Sebbene ben parallelo, per qualche motivo sta eseguendo la scansione del PK / CX. Ew. Il piano ha un costo di 2198 dollari di query.

Noccioline

Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 29532 ms,  elapsed time = 5828 ms.

È una scelta strana, perché se lo forziamo a utilizzare l'indice non cluster, il costo scende in modo significativo a 1798 query buck.

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Ehi, cerca! Dai un'occhiata laggiù. Nota anche che con la magia di CROSS APPLY, non abbiamo bisogno di fare qualcosa di sciocco per avere un piano per lo più completamente parallelo.

Noccioline

Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 27625 ms,  elapsed time = 4909 ms.

L'applicazione incrociata finisce per andare meglio senza le COUNTcose lì dentro.

SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY 
(
    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Il piano sembra buono, ma le letture e la CPU non sono un miglioramento.

Noccioline

Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 4844 ms,  elapsed time = 863 ms.

La riscrittura della croce si applica per essere un derivato derivato nello stesso identico tutto. Non ho intenzione di ripubblicare il piano di query e le informazioni sulle statistiche, in realtà non sono cambiate.

SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN 
(
    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x ON x.Id = u.Id;

Algebra relazionale : per essere accurati e per impedire a Joe Celko di perseguitare i miei sogni, dobbiamo almeno provare alcune strane cose relazionali. Qui non succede niente!

Un tentativo con INTERSECT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   INTERSECT
                   SELECT u.Age WHERE u.Age IS NOT NULL );

Noccioline

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 1094 ms,  elapsed time = 1090 ms.

Ed ecco un tentativo con EXCEPT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   EXCEPT
                   SELECT u.Age WHERE u.Age IS NULL);

Noccioline

Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 2126 ms,  elapsed time = 376 ms.

Potrebbero esserci altri modi per scriverli, ma lo lascerò alle persone che forse usano EXCEPTe INTERSECTpiù spesso di me.

Se hai davvero bisogno di un conteggio che uso COUNTnelle mie query come un po 'di stenografia (leggi: sono troppo pigro per inventare scenari più coinvolti a volte). Se hai solo bisogno di un conteggio, puoi usare CASEun'espressione per fare quasi la stessa cosa.

SELECT SUM(CASE WHEN u.Age < 18 THEN 1
                WHEN u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

Entrambi hanno lo stesso piano e hanno la stessa CPU e caratteristiche di lettura.

Noccioline

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 719 ms,  elapsed time = 719 ms.

Il vincitore? Nei miei test, il piano parallelo forzato con SUM su una tabella derivata ha dato i risultati migliori. E sì, molte di queste query avrebbero potuto essere aiutate aggiungendo un paio di indici filtrati per tenere conto di entrambi i predicati, ma volevo lasciare qualche sperimentazione ad altri.

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Grazie!


1
Le NOT EXISTS ( INTERSECT / EXCEPT )query possono funzionare senza le INTERSECT / EXCEPTparti: un WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );altro modo - che utilizza EXCEPT: SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ; (dove UserID è il PK o qualsiasi colonna non nulla univoca).
ypercubeᵀᴹ

Questo è stato testato? SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;Scusa se mi sono perso nelle milioni di versioni che hai testato!
ypercubeᵀᴹ

@ ypercubeᵀᴹ ecco il piano per quello. È un po 'diverso, ma ha caratteristiche simili ai UNION ALLpiani (CPU a 360ms, letture 11k).
Erik Darling,

Ehi Erik, stava girovagando per il mondo di sql ed è entrato per dire "colonna calcolata" solo per infastidirti. <3
crogiolo

17

Non ero pronto a ripristinare un database da 110 GB per una sola tabella, quindi ho creato i miei dati . Le distribuzioni di età dovrebbero corrispondere a ciò che è in Stack Overflow ma ovviamente la tabella stessa non corrisponderà. Non penso che sia troppo un problema perché le query colpiranno comunque gli indici. Sto testando su un computer a 4 CPU con SQL Server 2016 SP1. Una cosa da notare è che per le query che finiscono così rapidamente è importante non includere il piano di esecuzione effettivo. Questo può rallentare un po 'le cose.

Ho iniziato esaminando alcune delle soluzioni nella risposta eccellente di Erik. Per questo:

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Ho ottenuto i seguenti risultati da sys.dm_exec_sessions in 10 prove (la query naturalmente è andata parallela per me):

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

La query che ha funzionato meglio per Erik in realtà ha funzionato peggio sulla mia macchina:

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Risultati di 10 prove:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

Non sono immediatamente in grado di spiegare perché è così male, ma non è chiaro perché vogliamo forzare quasi tutti gli operatori nel piano di query a procedere in parallelo. Nel piano originale abbiamo una zona seriale che trova tutte le righe con AGE < 18. Ci sono solo poche migliaia di righe. Sulla mia macchina ottengo 9 letture logiche per quella parte della query e 9 ms di tempo CPU riportato e tempo trascorso. C'è anche una zona seriale per l'aggregato globale per le righe AGE IS NULLma che elabora solo una riga per DOP. Sulla mia macchina sono solo quattro file.

Il mio takeaway è che è molto importante ottimizzare la parte della query che trova le righe con un NULLfor Ageperché ci sono milioni di quelle righe. Non sono stato in grado di creare un indice con meno pagine che coprivano i dati di un semplice compresso sulla colonna nella colonna. Suppongo che ci sia una dimensione minima dell'indice per riga o che gran parte dello spazio dell'indice non possa essere evitato con i trucchi che ho provato. Quindi se siamo bloccati con lo stesso numero di letture logiche per ottenere i dati, l'unico modo per renderlo più veloce è rendere la query più parallela, ma questo deve essere fatto in modo diverso rispetto alla query di Erik che ha utilizzato TF 8649. Nella query sopra abbiamo un rapporto di 3,62 per il tempo CPU e il tempo trascorso che è abbastanza buono. L'ideale sarebbe un rapporto di 4.0 sulla mia macchina.

Una possibile area di miglioramento è quella di dividere il lavoro in modo più uniforme tra i thread. Nella schermata qui sotto possiamo vedere che una delle mie CPU ha deciso di fare una piccola pausa:

filo pigro

La scansione dell'indice è uno dei pochi operatori che possono essere implementati in parallelo e non possiamo fare nulla sul modo in cui le righe sono distribuite ai thread. C'è anche un elemento di possibilità, ma in modo abbastanza coerente ho visto un filo mal lavorato. Un modo per aggirare questo è fare il parallelismo nel modo più duro: nella parte interna di un loop nidificato. Qualsiasi cosa nella parte interna di un ciclo nidificato verrà implementata in modo seriale ma molti thread seriali possono essere eseguiti contemporaneamente. Finché otteniamo un metodo di distribuzione parallela favorevole (come round robin), possiamo controllare esattamente quante righe vengono inviate a ciascun thread.

Sto eseguendo query con DOP 4, quindi devo dividere uniformemente le NULLrighe nella tabella in quattro secchi. Un modo per farlo è creare un mucchio di indici su colonne calcolate:

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

Non sono del tutto sicuro del motivo per cui quattro indici separati sono leggermente più veloci di un indice, ma è quello che ho trovato nei miei test.

Per ottenere un piano di ciclo nidificato parallelo utilizzerò il flag di traccia non documentato 8649 . Scriverò anche il codice un po 'stranamente per incoraggiare l'ottimizzatore a non elaborare più righe del necessario. Di seguito è un'implementazione che sembra funzionare bene:

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

I risultati di dieci prove:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

Con questa query abbiamo un rapporto CPU-tempo trascorso di 3,85! Ci siamo rasati 17 ms dal runtime e ci sono voluti solo 4 colonne e indici calcolati per farlo! Ogni thread elabora molto vicino allo stesso numero di righe perché ogni indice ha molto vicino allo stesso numero di righe e ogni thread analizza solo un indice:

lavoro ben diviso

Infine, possiamo anche premere il pulsante facile e aggiungere un CCI non cluster alla Agecolonna:

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

La seguente query termina in 3 ms sul mio computer:

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

Sarà difficile da battere.


7

Anche se non ho una copia locale del database Stack Overflow, sono stato in grado di provare un paio di query. Il mio pensiero era quello di ottenere un conteggio degli utenti da una vista del catalogo di sistema (invece di ottenere direttamente un conteggio delle righe dalla tabella sottostante). Quindi ottieni un conteggio di righe che corrispondono (o forse non corrispondono) ai criteri di Erik e fai qualche semplice calcolo matematico.

Ho usato Stack Exchange Data Explorer (insieme a SET STATISTICS TIME ON;e SET STATISTICS IO ON;) per testare le query. Per un punto di riferimento, ecco alcune query e le statistiche CPU / IO:

QUERY 1

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 0 ms. (1 riga (e) restituita)

Tabella "Utenti". Conteggio scansioni 17, letture logiche 201567, letture fisiche 0, letture avanti 2740, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 1829 ms, tempo trascorso = 296 ms.

QUERY 2

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 0 ms. (1 riga (e) restituita)

Tabella "Utenti". Conteggio scansioni 17, letture logiche 201567, letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 2500 ms, tempo trascorso = 147 ms.

QUERY 3

--Erik's derived tables/UNION ALL query.
SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 0 ms. (1 riga (e) restituita)

Tabella "Utenti". Conteggio scansioni 34, letture logiche 403134, letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 3156 ms, tempo trascorso = 215 ms.

1 ° tentativo

Questo è stato più lento di tutte le domande di Erik che ho elencato qui ... almeno in termini di tempo trascorso.

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 0 ms. (1 riga (e) restituita)

Tabella "Tavolo da lavoro". Conteggio scansioni 0, letture logiche 0, letture fisiche 0, letture read-ahead 0, letture logiche lob 0, letture fisiche lob 0, letture read avanti lob 0. Letture "sysrowset". Conteggio scansioni 2, letture logiche 10, letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0. Tabella 'sysschobjs'. Conteggio scansioni 1, letture logiche 4, letture fisiche 0, letture read-ahead 0, letture logiche lob 0, letture fisiche lob 0, letture read avanti lob 0. Tabella "Utenti". Conteggio scansioni 1, letture logiche 201567, letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 593 ms, tempo trascorso = 598 ms.

2 ° tentativo

Qui ho optato per una variabile per memorizzare il numero totale di utenti (anziché una query secondaria). Il conteggio delle scansioni è aumentato da 1 a 17 rispetto al primo tentativo. Le letture logiche sono rimaste le stesse. Tuttavia, il tempo trascorso è sceso considerevolmente.

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 0 ms. Tabella "Tavolo da lavoro". Conteggio scansioni 0, letture logiche 0, letture fisiche 0, letture read-ahead 0, letture logiche lob 0, letture fisiche lob 0, letture read avanti lob 0. Letture "sysrowset". Conteggio scansioni 2, letture logiche 10, letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0. Tabella 'sysschobjs'. Conteggio scansioni 1, letture logiche 4, letture fisiche 0, letture avanti 0, letture logiche lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 1 ms. (1 riga (e) restituita)

Tabella "Utenti". Conteggio scansioni 17, letture logiche 201567, letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 1471 ms, tempo trascorso = 98 ms.

Altre note: DBCC TRACEON non è consentito su Stack Exchange Data Explorer, come indicato di seguito:

L'utente "STACKEXCHANGE \ svc_sede" non dispone dell'autorizzazione per eseguire DBCC TRACEON.


1
Probabilmente non hanno gli stessi indici che ho io, quindi le differenze. E chi lo sa? Forse il mio server di casa è su hardware migliore;) Ottima risposta però!
Erik Darling,

avresti dovuto usare la seguente query per il tuo primo tentativo (sarà molto più veloce, poiché elimina gran parte degli oggetti di sistema): SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
Thomas Franz

PS: tieni presente che gli indici In-Memory (NONCLUSTERED HASH) non hanno un indice id = 0/1 come avrebbe un indice heap / cluster comune)
Thomas Franz

1

Usa le variabili?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

Per il commento può saltare le variabili

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
Inoltre:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
Potrei provare a farlo controllando CPU e I / O. Suggerimento: è uguale a una delle risposte di Erik.
Brent Ozar,

0

Bene usando SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

Questo è qualcosa che mi è appena venuto in mente. Ho appena eseguito questo in https://data.stackexchange.com

Ma non efficiente quanto @blitz_erik però


0

Una soluzione banale è calcolare count (*) - count (age> = 18):

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

O:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

Risultati qui

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.