it-swarm.com.de

Welche verschiedenen Möglichkeiten gibt es, ISNULL () in einer WHERE-Klausel zu ersetzen, die nur Literalwerte verwendet?

Worum es hier nicht geht:

Dies ist keine Frage zu Catch-All-Abfragen , die Benutzereingaben akzeptieren oder Variablen verwenden.

Hierbei handelt es sich ausschließlich um Abfragen, bei denen ISNULL() in der Klausel WHERE verwendet wird, um NULL -Werte durch einen kanarischen Wert zum Vergleich mit einem Prädikat zu ersetzen, und um diese Abfragen auf verschiedene Arten neu zu schreiben zu sein SARGable in SQL Server.

Warum hast du da drüben keinen Platz?

Unsere Beispielabfrage bezieht sich auf eine lokale Kopie der Stapelüberlaufdatenbank unter SQL Server 2016 und sucht nach Benutzern mit einem NULL Alter oder einem Alter <18.

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

Der Abfrageplan zeigt einen Scan eines recht durchdachten, nicht gruppierten Index.

(Nuts

Der Scan-Operator zeigt (dank Ergänzungen des tatsächlichen XML-Ausführungsplans in neueren Versionen von SQL Server), dass wir jede stinkende Zeile lesen.

(Nuts

Insgesamt führen wir 9157 Lesevorgänge durch und verwenden etwa eine halbe Sekunde CPU-Zeit:

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.

Die Frage: Wie kann diese Abfrage umgeschrieben werden, um sie effizienter und vielleicht sogar SARGable zu machen?

Fühlen Sie sich frei, andere Vorschläge zu machen. Ich denke nicht, dass meine Antwort unbedingt die Antwort ist, und es gibt genug kluge Leute da draußen, um Alternativen zu finden, die vielleicht besser sind.

Wenn Sie auf Ihrem eigenen Computer mitspielen möchten, gehen Sie hier zu laden Sie die Datenbank SO herunter.

Vielen Dank!

55
Erik Darling

Antwortabschnitt

Es gibt verschiedene Möglichkeiten, dies mithilfe verschiedener T-SQL-Konstrukte neu zu schreiben. Wir werden uns die Vor- und Nachteile ansehen und unten einen Gesamtvergleich durchführen.

Zuerst : Verwenden von OR

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

Durch die Verwendung von OR erhalten wir einen effizienteren Suchplan, der die genaue Anzahl der benötigten Zeilen liest, jedoch dem Abfrageplan das hinzufügt, was die technische Welt a whole mess of malarkey Nennt.

(Nuts

Beachten Sie auch, dass die Suche hier zweimal ausgeführt wird, was für den grafischen Operator wirklich offensichtlicher sein sollte:

(Nuts

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.

Zweitens : Abgeleitete Tabellen mit UNION ALL Verwenden Unsere Abfrage kann auch so umgeschrieben werden

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);

Dies ergibt die gleiche Art von Plan mit weitaus weniger Malarkey und einem offensichtlicheren Grad an Ehrlichkeit darüber, wie oft der Index gesucht (gesucht?) Wurde.

(Nuts

Es führt die gleiche Anzahl von Lesevorgängen (8233) durch wie die Abfrage OR, spart jedoch etwa 100 ms CPU-Freizeit.

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

Allerdings muss man hier wirklich vorsichtig sein, denn wenn dieser Plan versucht, parallel zu verlaufen, werden die beiden separaten COUNT -Operationen serialisiert, da sie jeweils als global betrachtet werden Skalaraggregat. Wenn wir einen parallelen Plan mit dem Trace-Flag 8649 erzwingen, wird das Problem offensichtlich.

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);

(Nuts

Dies kann vermieden werden, indem Sie unsere Abfrage geringfügig ändern.

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);

Jetzt sind beide Knoten, die eine Suche ausführen, vollständig parallelisiert, bis wir den Verkettungsoperator treffen.

(Nuts

Für das, was es wert ist, hat die vollständig parallele Version einige gute Vorteile. Bei Kosten von etwa 100 weiteren Lesevorgängen und etwa 90 ms zusätzlicher CPU-Zeit verringert sich die verstrichene Zeit auf 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.

Was ist mit CROSS APPLY? Ohne die Magie von CROSS APPLY Ist keine Antwort vollständig!

Leider haben wir mehr Probleme mit 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);

Dieser Plan ist schrecklich. Dies ist die Art von Plan, mit der Sie enden, wenn Sie zuletzt zum St. Patrick's Day erscheinen. Obwohl schön parallel, scannt es aus irgendeinem Grund die PK/CX. Ew. Der Plan kostet 2198 Abfrageböcke.

(Nuts

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.

Das ist eine seltsame Wahl, denn wenn wir die Verwendung des nicht gruppierten Index erzwingen, sinken die Kosten erheblich auf 1798 Abfrageböcke.

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);

Hey, sucht! Schau dich da drüben an. Beachten Sie auch, dass wir mit der Magie von CROSS APPLY Nichts doofes tun müssen, um einen größtenteils vollständig parallelen Plan zu haben.

(Nuts

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.

Cross Apply geht es am Ende besser, ohne das COUNT Zeug drin.

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);

Der Plan sieht gut aus, aber die Lesevorgänge und die CPU sind keine Verbesserung.

(Nuts

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.

Das Umschreiben des Kreuzes als abgeleitete Verknüpfung führt zu genau demselben Ergebnis. Ich werde den Abfrageplan und die Statistikinformationen nicht erneut veröffentlichen - sie haben sich wirklich nicht geändert.

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;

Relationale Algebra : Um gründlich zu sein und Joe Celko davon abzuhalten, meine Träume zu verfolgen, müssen wir zumindest einige seltsame relationale Dinge ausprobieren. Hier geht nichts!

Ein Versuch mit 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 );

(Nuts

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.

Und hier ist ein Versuch mit 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);

(Nuts

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.

Es mag andere Möglichkeiten geben, diese zu schreiben, aber ich überlasse das den Leuten, die vielleicht öfter EXCEPT und INTERSECT verwenden als ich.

Wenn Sie wirklich nur eine Zählung benötigen , verwende ich COUNT in meinen Abfragen als Abkürzung (lesen Sie: Ich bin zu faul dafür manchmal mit komplexeren Szenarien aufwarten). Wenn Sie nur eine Zählung benötigen, können Sie einen CASE - Ausdruck verwenden, um genau dasselbe zu tun.

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

Beide haben den gleichen Plan und die gleichen CPU- und Leseeigenschaften.

(Nuts

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.

Der Gewinner? In meinen Tests schnitt der erzwungene Parallelplan mit SUM über eine abgeleitete Tabelle am besten ab. Und ja, viele dieser Abfragen hätten durch Hinzufügen einiger gefilterter Indizes unterstützt werden können, um beide Prädikate zu berücksichtigen, aber ich wollte einige Experimente anderen überlassen.

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);

Vielen Dank!

58
Erik Darling

Ich war nicht bereit, eine 110-GB-Datenbank für nur eine Tabelle wiederherzustellen, also ich habe meine eigenen Daten erstellt . Die Altersverteilungen sollten mit dem Stapelüberlauf übereinstimmen, aber offensichtlich stimmt die Tabelle selbst nicht überein. Ich denke nicht, dass es ein zu großes Problem ist, da die Abfragen sowieso die Indizes treffen werden. Ich teste auf einem 4-CPU-Computer mit SQL Server 2016 SP1. Zu beachten ist, dass es bei Abfragen, die dies schnell erledigen, wichtig ist, den tatsächlichen Ausführungsplan nicht einzuschließen. Das kann die Dinge ziemlich verlangsamen.

Ich begann damit, einige der Lösungen in Eriks ausgezeichneter Antwort durchzugehen. Für dieses:

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);

Ich habe die folgenden Ergebnisse von sys.dm_exec_sessions über 10 Versuche erhalten (die Abfrage verlief für mich natürlich parallel):

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

Die Abfrage, die für Erik besser funktionierte, lief auf meinem Computer tatsächlich schlechter:

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);

Ergebnisse aus 10 Studien:

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

Ich kann nicht sofort erklären, warum es so schlimm ist, aber es ist nicht klar, warum wir fast jeden Operator im Abfrageplan dazu zwingen wollen, parallel zu arbeiten. Im ursprünglichen Plan haben wir eine serielle Zone, die alle Zeilen mit AGE < 18 Findet. Es gibt nur wenige tausend Zeilen. Auf meinem Computer erhalte ich 9 logische Lesevorgänge für diesen Teil der Abfrage und 9 ms gemeldete CPU-Zeit und verstrichene Zeit. Es gibt auch eine serielle Zone für das globale Aggregat für die Zeilen mit AGE IS NULL, Die jedoch nur eine Zeile pro DOP verarbeitet. Auf meinem Computer sind dies nur vier Zeilen.

Meiner Meinung nach ist es am wichtigsten, den Teil der Abfrage zu optimieren, der Zeilen mit einem NULL für Age findet, da es Millionen dieser Zeilen gibt. Ich konnte keinen Index mit weniger Seiten erstellen, die die Daten abdeckten, als einen einfachen, seitenkomprimierten Index in der Spalte. Ich gehe davon aus, dass es eine Mindestindexgröße pro Zeile gibt oder dass ein Großteil des Indexbereichs mit den von mir versuchten Tricks nicht vermieden werden kann. Wenn wir also ungefähr die gleiche Anzahl logischer Lesevorgänge haben, um die Daten zu erhalten, besteht die einzige Möglichkeit, sie schneller zu machen, darin, die Abfrage paralleler zu gestalten. Dies muss jedoch anders erfolgen als bei Eriks Abfrage, bei der TF verwendet wurde 8649. In der obigen Abfrage haben wir ein Verhältnis von 3,62 für die CPU-Zeit zur verstrichenen Zeit, was ziemlich gut ist. Das Ideal wäre ein Verhältnis von 4,0 auf meiner Maschine.

Ein möglicher Verbesserungsbereich besteht darin, die Arbeit gleichmäßiger auf die Fäden aufzuteilen. Im folgenden Screenshot sehen wir, dass eine meiner CPUs beschlossen hat, eine kleine Pause einzulegen:

(lazy thread

Der Index-Scan ist einer der wenigen Operatoren, die parallel implementiert werden können, und wir können nichts dagegen tun, wie die Zeilen auf Threads verteilt werden. Es gibt auch ein Element des Zufalls, aber ziemlich konsequent habe ich einen unterarbeiteten Thread gesehen. Eine Möglichkeit, dies zu umgehen, besteht darin, Parallelität auf die harte Tour durchzuführen: im inneren Teil eines verschachtelten Loop-Joins. Alles im inneren Teil einer verschachtelten Schleife wird seriell implementiert, aber viele serielle Threads können gleichzeitig ausgeführt werden. Solange wir eine günstige parallele Verteilungsmethode erhalten (z. B. Round Robin), können wir genau steuern, wie viele Zeilen an jeden Thread gesendet werden.

Ich führe Abfragen mit DOP 4 aus, daher muss ich die NULL - Zeilen in der Tabelle gleichmäßig in vier Buckets aufteilen. Eine Möglichkeit, dies zu tun, besteht darin, eine Reihe von Indizes für berechnete Spalten zu erstellen:

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);

Ich bin mir nicht ganz sicher, warum vier separate Indizes etwas schneller sind als ein Index, aber das habe ich bei meinen Tests festgestellt.

Um einen parallelen verschachtelten Schleifenplan zu erhalten, verwende ich das undokumentierte Trace-Flag 8649 . Ich werde den Code auch etwas seltsam schreiben, um den Optimierer zu ermutigen, nicht mehr Zeilen als nötig zu verarbeiten. Im Folgenden finden Sie eine Implementierung, die anscheinend gut funktioniert:

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);

Die Ergebnisse von zehn Studien:

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

Mit dieser Abfrage haben wir ein Verhältnis von CPU zu verstrichener Zeit von 3,85! Wir haben uns 17 ms von der Laufzeit entfernt und es wurden nur 4 berechnete Spalten und Indizes benötigt, um dies zu tun! Jeder Thread verarbeitet insgesamt sehr nahe an der gleichen Anzahl von Zeilen, da jeder Index sehr nahe an der gleichen Anzahl von Zeilen liegt und jeder Thread nur einen Index scannt:

(well divided work

Abschließend können wir auch auf die einfache Schaltfläche klicken und der Spalte Age eine nicht gruppierte CCI hinzufügen:

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

Die folgende Abfrage wird auf meinem Computer in 3 ms beendet:

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

Das wird schwer zu schlagen sein.

18
Joe Obbish

Obwohl ich keine lokale Kopie der Stapelüberlaufdatenbank habe, konnte ich einige Abfragen ausprobieren. Mein Gedanke war, eine Anzahl von Benutzern aus einer Systemkatalogansicht zu erhalten (im Gegensatz zu einer direkten Anzahl von Zeilen aus der zugrunde liegenden Tabelle). Holen Sie sich dann eine Anzahl von Zeilen, die Eriks Kriterien entsprechen (oder vielleicht auch nicht), und rechnen Sie einfach.

Ich habe den Stack Exchange Data Explorer (zusammen mit SET STATISTICS TIME ON; und SET STATISTICS IO ON;) um die Abfragen zu testen. Als Referenz hier einige Abfragen und die CPU/IO-Statistiken:

FRAGE 1

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

SQL Server-Ausführungszeiten: CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms. (1 Zeile (n) zurückgegeben)

Tabelle 'Benutzer'. Scananzahl 17, logische Lesevorgänge 201567, physische Lesevorgänge 0, Vorauslesevorgänge 2740, logische Vorlesevorgänge 0, physikalische Vorlesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 1829 ms, verstrichene Zeit = 296 ms.

FRAGE 2

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

SQL Server-Ausführungszeiten: CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms. (1 Zeile (n) zurückgegeben)

Tabelle 'Benutzer'. Scananzahl 17, logische Lesevorgänge 201567, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 2500 ms, verstrichene Zeit = 147 ms.

FRAGE 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);

SQL Server-Ausführungszeiten: CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms. (1 Zeile (n) zurückgegeben)

Tabelle 'Benutzer'. Scananzahl 34, logische Lesevorgänge 403134, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 3156 ms, verstrichene Zeit = 215 ms.

1. Versuch

Dies war langsamer als alle Anfragen von Erik, die ich hier aufgelistet habe ... zumindest in Bezug auf die verstrichene Zeit.

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

SQL Server-Ausführungszeiten: CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms. (1 Zeile (n) zurückgegeben)

Tabelle 'Arbeitstisch'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0. Tabelle 'sysrowsets'. Scananzahl 2, logische Lesevorgänge 10, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0. Tabelle 'sysschobjs'. Scananzahl 1, logische Lesevorgänge 4, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0. Tabelle 'Benutzer'. Scananzahl 1, logische Lesevorgänge 201567, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 593 ms, verstrichene Zeit = 598 ms.

2. Versuch

Hier habe ich mich für eine Variable entschieden, um die Gesamtzahl der Benutzer zu speichern (anstelle einer Unterabfrage). Die Anzahl der Scans wurde im Vergleich zum ersten Versuch von 1 auf 17 erhöht. Die logischen Lesevorgänge blieben unverändert. Die verstrichene Zeit ging jedoch erheblich zurück.

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

SQL Server-Ausführungszeiten: CPU-Zeit = 0 ms, verstrichene Zeit = 0 ms. Tabelle 'Arbeitstisch'. Scananzahl 0, logische Lesevorgänge 0, physische Lesevorgänge 0, Vorauslesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0. Tabelle 'sysrowsets'. Scananzahl 2, logische Lesevorgänge 10, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0. Tabelle 'sysschobjs'. Scananzahl 1, logische Lesevorgänge 4, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Lobvorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 0 ms, verstrichene Zeit = 1 ms. (1 Zeile (n) zurückgegeben)

Tabelle 'Benutzer'. Scananzahl 17, logische Lesevorgänge 201567, physische Lesevorgänge 0, Vorlesevorgänge 0, Lob-Lesevorgänge 0, Lob-Lesevorgänge 0, Vorlesevorgänge 0.

SQL Server-Ausführungszeiten: CPU-Zeit = 1471 ms, verstrichene Zeit = 98 ms.

Sonstige Hinweise: DBCC TRACEON ist im Stack Exchange Data Explorer nicht zulässig, wie unten angegeben:

Der Benutzer 'STACKEXCHANGE\svc_sede' hat keine Berechtigung zum Ausführen von DBCC TRACEON.

7
Dave Mason

Variablen verwenden?

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 Kommentar können die Variablen übersprungen werden

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

Eine triviale Lösung besteht darin, Anzahl (*) - Anzahl (Alter> = 18) zu berechnen:

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

Oder:

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

Ergebnisse hier

0
Salman A

Gut mit 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.

Dies ist etwas, das mir gerade in den Sinn gekommen ist. Ich habe es gerade in https://data.stackexchange.com ausgeführt

Aber nicht so effizient wie @blitz_erik

0
Biju jose