CROSS APPLY erzeugt eine äußere Verbindung

17

Als Antwort auf die SQL-Zählung, die sich von der Partition unterscheidet, hat Erik Darling diesen Code veröffentlicht, um das Problem zu COUNT(DISTINCT) OVER ()umgehen.

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY (   SELECT COUNT(DISTINCT mt2.Col_B) AS dc
                FROM   #MyTable AS mt2
                WHERE  mt2.Col_A = mt.Col_A
                -- GROUP BY mt2.Col_A 
            ) AS ca;

Die Abfrage verwendet CROSS APPLY(nicht OUTER APPLY). Warum gibt es im Ausführungsplan einen äußeren Join anstelle eines inneren Joins?

Bildbeschreibung hier eingeben

Warum führt das Entfernen des Kommentars von group by zu einer inneren Verknüpfung?

Bildbeschreibung hier eingeben

Ich denke nicht, dass die Daten wichtig sind, sondern dass sie von den Daten kopiert werden, die Kevin What zu der anderen Frage gegeben hat:

create table #MyTable (
Col_A varchar(5),
Col_B int
)

insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)

insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)
Paul White sagt GoFundMonica
quelle

Antworten:

22

Zusammenfassung

SQL Server verwendet die richtige Verknüpfung (innere oder äußere) und fügt bei Bedarf Projektionen hinzu, um alle Semantiken der ursprünglichen Abfrage zu berücksichtigen, wenn interne Übersetzungen zwischen Anwenden und Verknüpfen ausgeführt werden .

Die Unterschiede in den Plänen lassen sich alle durch die unterschiedliche Semantik von Aggregaten mit und ohne Group-by-Klausel in SQL Server erklären .


Einzelheiten

Join vs Apply

Wir müssen in der Lage sein, zwischen einem Apply und einem Join zu unterscheiden :

  • Anwenden

    Die innere (untere) Eingabe von " apply" wird für jede Zeile der äußeren (oberen) Eingabe ausgeführt, wobei ein oder mehrere innere Seitenparameterwerte von der aktuellen äußeren Zeile bereitgestellt werden. Das Gesamtergebnis der Anwendung ist die Kombination (Vereinigung aller) aller Zeilen, die durch die parametrisierten Ausführungen der Innenseite erzeugt werden. Das Vorhandensein von Parametern bedeutet, dass apply manchmal als korrelierter Join bezeichnet wird.

    Ein Apply wird in Ausführungsplänen immer vom Nested Loops- Operator implementiert . Der Operator verfügt über eine Outer References- Eigenschaft, anstatt Prädikate zu verknüpfen. Die äußeren Referenzen sind die Parameter, die bei jeder Iteration der Schleife von der Außenseite zur Innenseite übergeben werden.

  • Beitreten

    Ein Join wertet sein Join-Prädikat beim Join-Operator aus. Der Join kann im Allgemeinen von Hash Match- , Merge- oder Nested Loops- Operatoren in SQL Server implementiert werden .

    Wenn verschachtelte Schleifen ausgewählt werden, kann dies durch das Fehlen äußerer Referenzen (und normalerweise durch das Vorhandensein eines Join-Prädikats) von einer Anwendung unterschieden werden . Die innere Eingabe eines Joins verweist nie auf Werte aus der äußeren Eingabe. Die innere Seite wird immer noch einmal für jede äußere Zeile ausgeführt, aber die Ausführung der inneren Seite hängt nicht von Werten aus der aktuellen äußeren Zeile ab.

Weitere Details finden Sie in meinem Beitrag Apply versus Nested Loops Join .

... warum gibt es im Ausführungsplan eine äußere Verknüpfung anstelle einer inneren Verknüpfung?

Die äußere Verknüpfung entsteht, wenn das Optimierungsprogramm eine Anwendung auf eine Verknüpfung (unter Verwendung einer aufgerufenen Regel ApplyHandler) umwandelt , um festzustellen, ob ein kostengünstiger auf Verknüpfungen basierender Plan gefunden werden kann. Die Verknüpfung muss eine äußere Verknüpfung sein, um die Richtigkeit zu gewährleisten, wenn die Anwendung ein skalares Aggregat enthält . Es kann nicht garantiert werden , dass eine innere Verknüpfung die gleichen Ergebnisse wie die ursprüngliche Verknüpfung liefert, wie wir sehen werden.

Skalar- und Vektoraggregate

  • Ein Aggregat ohne entsprechende GROUP BYKlausel ist ein skalares Aggregat.
  • Ein Aggregat mit einer entsprechenden GROUP BYKlausel ist ein Vektor - Aggregat.

In SQL Server erzeugt ein skalares Aggregat immer eine Zeile, auch wenn es keine zu aggregierenden Zeilen erhält. Beispielsweise ist das skalare COUNTAggregat ohne Zeilen Null. Ein Vektor COUNT - Aggregat keine Zeilen ist die leere Menge (keine Zeilen überhaupt).

Die folgenden Spielzeugabfragen veranschaulichen den Unterschied. Weitere Informationen zu Skalar- und Vektoraggregaten finden Sie in meinem Artikel Spaß mit Skalar- und Vektoraggregaten .

-- Produces a single zero value
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1;

-- Produces no rows
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1 GROUP BY ();

db <> Geige Demo

Transformieren anwenden, um beizutreten

Ich habe bereits erwähnt, dass der Join ein äußerer Join sein muss, damit er korrekt ist, wenn das ursprüngliche Apply ein skalares Aggregat enthält . Um zu zeigen, warum dies im Detail der Fall ist, werde ich ein vereinfachtes Beispiel der Fragenabfrage verwenden:

DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);

INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);

SELECT * FROM @A AS A
CROSS APPLY (SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A) AS CA;

Das richtige Ergebnis für die Spalte cist Null , da COUNT_BIGes sich um ein skalares Aggregat handelt. Bei der Übersetzung dieser Apply-Abfrage für das Beitrittsformular generiert SQL Server eine interne Alternative, die wie folgt aussehen würde, wenn sie in T-SQL ausgedrückt würde:

SELECT A.*, c = COALESCE(J1.c, 0)
FROM @A AS A
LEFT JOIN
(
    SELECT B.A, c = COUNT_BIG(*) 
    FROM @B AS B
    GROUP BY B.A
) AS J1
    ON J1.A = A.A;

Um das Apply als unkorrelierten Join umzuschreiben, müssen Sie ein GROUP BYin die abgeleitete Tabelle einfügen (andernfalls ist möglicherweise keine ASpalte zum Verknüpfen vorhanden). Der Join muss ein Outer- Join sein, damit jede Zeile aus der Tabelle @Aweiterhin eine Zeile in der Ausgabe erzeugt. Der linke Join erzeugt eine NULLfor-Spalte, cwenn das Join-Prädikat nicht mit true ausgewertet wird. Das NULLmuss bis auf Null übersetzt werden COALESCE, um eine korrekte Transformation von Apply abzuschließen .

In der folgenden Demo wird gezeigt, wie sowohl Outer Join als auch Outer Join COALESCEerforderlich sind, um dieselben Ergebnisse mit Join als ursprünglicher Apply- Abfrage zu erzielen :

db <> Geige Demo

Mit dem GROUP BY

... warum führt das Auskommentieren der group by-Klausel zu einer inneren Verknüpfung?

Fortsetzung des vereinfachten Beispiels, aber Hinzufügen eines GROUP BY:

DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);

INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);

-- Original
SELECT * FROM @A AS A
CROSS APPLY 
(SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A GROUP BY B.A) AS CA;

Das COUNT_BIGist nun ein Vektor Aggregat, so dass das richtige Ergebnis für ein leeres Eingabemenge ist nicht mehr Null ist , ist es keine Zeile überhaupt . Mit anderen Worten, das Ausführen der obigen Anweisungen führt zu keiner Ausgabe.

Diese Semantik ist viel einfacher zu berücksichtigen, wenn Sie vom Anwenden zum Verbinden übersetzen , da CROSS APPLYnatürlich jede äußere Zeile abgelehnt wird, die keine inneren Seitenzeilen erzeugt. Aus diesem Grund können wir jetzt eine innere Verknüpfung ohne zusätzliche Ausdrucksprojektion verwenden:

-- Rewrite
SELECT A.*, J1.c 
FROM @A AS A
JOIN
(
    SELECT B.A, c = COUNT_BIG(*) 
    FROM @B AS B
    GROUP BY B.A
) AS J1
    ON J1.A = A.A;

Die folgende Demo zeigt, dass das Umschreiben des inneren Joins dieselben Ergebnisse wie das ursprüngliche Anwenden mit Vektoraggregat liefert:

db <> Geige Demo

Das Optimierungsprogramm wählt zufällig einen Merge-Inner-Join mit der kleinen Tabelle, da es schnell einen günstigen Join- Plan findet (gut genug, um einen Plan zu finden). Das kostenbasierte Optimierungsprogramm schreibt den Join möglicherweise wieder in einen Apply um - möglicherweise wird ein günstigerer Apply-Plan gefunden, wie dies hier der Fall ist, wenn ein Loop-Join oder Forceseek-Hinweis verwendet wird - aber in diesem Fall lohnt sich der Aufwand nicht.

Anmerkungen

In den vereinfachten Beispielen werden verschiedene Tabellen mit unterschiedlichen Inhalten verwendet, um die semantischen Unterschiede deutlicher darzustellen.

Man könnte argumentieren, dass der Optimierer in der Lage sein sollte, zu argumentieren, dass ein Self-Join keine nicht übereinstimmenden (nicht verbundenen) Zeilen erzeugen kann, aber er enthält diese Logik heute nicht. Es ist nicht garantiert, dass ein mehrmaliger Zugriff auf dieselbe Tabelle in einer Abfrage im Allgemeinen zu denselben Ergebnissen führt, je nach Isolationsstufe und gleichzeitiger Aktivität.

Das Optimierungsprogramm kümmert sich um diese Semantik und Kantenfälle, sodass Sie dies nicht tun müssen.


Bonus: Inner Apply Plan

SQL Server kann einen inneren Anwendungsplan (keinen inneren Verknüpfungsplan !) Für die Beispielabfrage erstellen. Aus Kostengründen wird nur darauf verzichtet. Die Kosten für den in der Frage gezeigten Outer Join-Plan betragen 0,02898 Einheiten auf der SQL Server 2017-Instanz meines Laptops.

Sie können einen Apply- Plan (korrelierter Join) erzwingen , indem Sie das nicht dokumentierte und nicht unterstützte Ablaufverfolgungsflag 9114 (das ApplyHandlerusw. deaktiviert ) nur zur Veranschaulichung verwenden:

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY 
(
    SELECT COUNT_BIG(DISTINCT mt2.Col_B) AS dc
    FROM   #MyTable AS mt2
    WHERE  mt2.Col_A = mt.Col_A 
    --GROUP BY mt2.Col_A
) AS ca
OPTION (QUERYTRACEON 9114);

Dies erzeugt eine Anwendung verschachtelte Schleifen Plans mit einer faulen Index Spule. Die geschätzten Gesamtkosten betragen 0,0463983 (höher als der ausgewählte Plan):

Index-Spool-Anwendungsplan

Beachten Sie, dass der Ausführungsplan, bei dem verschachtelte Schleifen angewendet werden, unter Verwendung der Semantik "Inner Join" korrekte Ergebnisse liefert, unabhängig vom Vorhandensein der GROUP BYKlausel.

In der realen Welt verfügen wir normalerweise über einen Index, der eine Suche auf der Innenseite der Anwendung unterstützt , um SQL Server zu ermutigen, diese Option auf natürliche Weise zu wählen. Beispiel:

CREATE INDEX i ON #MyTable (Col_A, Col_B);

db <> Geige Demo

Paul White sagt GoFundMonica
quelle
-3

Cross Apply ist eine logische Operation für die Daten. Bei der Entscheidung, wie diese Daten abgerufen werden sollen, wählt SQL Server den entsprechenden physischen Operator aus, um die gewünschten Daten abzurufen.

Es gibt keinen physischen Anwendungsoperator, und SQL Server übersetzt ihn in den geeigneten und hoffentlich effizienten Verknüpfungsoperator.

Eine Liste der physischen Operatoren finden Sie unter dem folgenden Link.

https://docs.microsoft.com/de-de/sql/relational-databases/showplan-logical-and-physical-operators-reference?view=sql-server-2017

Das Abfrageoptimierungsprogramm erstellt einen Abfrageplan als Baum, der aus logischen Operatoren besteht. Nachdem das Abfrageoptimierungsprogramm den Plan erstellt hat, wählt das Abfrageoptimierungsprogramm den effizientesten physischen Operator für jeden logischen Operator aus. Das Abfrageoptimierungsprogramm verwendet einen kostenbasierten Ansatz, um zu bestimmen, welcher physische Operator einen logischen Operator implementieren wird.

Normalerweise kann eine logische Operation von mehreren physischen Operatoren implementiert werden. In seltenen Fällen kann ein physischer Operator jedoch auch mehrere logische Operationen implementieren.

edit / Es scheint, als hätte ich deine Frage falsch verstanden. SQL Server wählt normalerweise den am besten geeigneten Operator. Ihre Abfrage muss nicht für alle Kombinationen beider Tabellen Werte zurückgeben, wenn ein Cross-Join verwendet wird. Es reicht aus, nur den gewünschten Wert für jede Zeile zu berechnen.

J. Maes
quelle