2016-07-28 39 views
1

Ich habe immer gedacht, dass With Klausel wurde als eine einmalige Ausführung Anweisung, die sich wie eine normale Tabelle verhält - Sie können alle SQL-Operationen auf sie als Sie in einer normalen Tabelle tun würde.With Klausel Ausführung

Es stellte sich jedoch heraus, dass in mehreren Datenbanken (Oracle, Netezza, Sybase, Teradata) die with-Klausel jedes Mal ausgeführt wird, wenn sie verwendet wird.

Anstelle von 2 identischen Zahlen gibt die obige Abfrage 2 verschiedene Zahlen zurück, so dass sie für jede Auswahl ausgeführt wird.

Wenn ich eine sehr komplexe Abfrage innerhalb einer With Klausel habe und ich es 5 mal im Rest der Abfrage verwende, würde es 5 mal ausführen, was mir sehr ineffektiv scheint.

Also kann mir jemand einen guten logischen Grund geben, funktioniert es so?

+0

Postgres gibt die gleiche Anzahl für beide äußeren auswählt. Der CTE wird also nur einmal ausgeführt. –

+0

In Oracle 'WITH test (rnd) AS (SELECT/* + MATERIALISIEREN */DBMS_RANDOM.VALUE FROM DUAL) SELECT 'a', t. * FROM test t UNION ALL SELECT 'b', t. * FROM test t; 'gibt für beide die gleiche Zahl. – MT0

Antwort

0

Für Oracle:

CTE aka Subquery Factoring-Klausel (in Oracle-Terminologie) können entweder inline oder materialisiert werden, was immer möglich ist. CBO sollte entscheiden, was effektiver sein wird. Ihr Beispiel mit der Funktion ramdom() ist ein Eckfall.

Versuchen Sie, Hinweise MATERIALIZE oder INLINE zu verwenden, um zu sehen, wie exec. Planänderungen. Ihr Test sagt nichts über das Verhalten der echten SQL-Abfrageauswertung aus.

1

In der oracle world, wie erklärt here, die WITH abfrage_name-Klausel können Sie einen Namen zu einem Unterabfrage-Block zuweisen. Sie können dann auf den Unterabfrageblock mehrere Stellen in der Abfrage verweisen, indem Sie den Abfragenamen angeben. Oracle optimiert die Abfrage, indem der Abfragename entweder als Inline-Ansicht oder als temporäre Tabelle behandelt wird.

Sie können diese Klausel in jeder SELECT-Anweisung auf oberster Ebene und in den meisten Arten von Unterabfragen angeben. Der Name der Abfrage ist für die Hauptabfrage und für alle nachfolgenden Unterabfragen sichtbar, mit Ausnahme der Unterabfrage, die den Abfragesamen selbst definiert.

Eine WITH-Klausel ist am wertvollsten, wenn das Ergebnis der WITH-Abfrage mehr als einmal im Hauptteil der Hauptabfrage benötigt wird, z. B. wenn ein Durchschnittswert zwei- oder dreimal verglichen werden soll. Der Zweck besteht darin, die Anzahl der Zugriffe auf eine Tabelle zu minimieren, die mehrfach in einer einzelnen Abfrage verknüpft ist.

Einschränkungen bei Subquery Factoring:

Sie können diese Klausel nicht verschachtelt werden. Das heißt, Sie können die subquery_factoring_clause nicht in der Unterabfrage einer anderen subquery_factoring_clause angeben. Ein query_name, der in einer Unterabfrage_factoring_klause definiert ist, kann jedoch in der Unterabfrage jeder nachfolgenden Unterabfrage_factoring_klausel verwendet werden.

In einer Abfrage mit Mengenoperatoren darf die Mengenoperator-Unterabfrage nicht die Unterabfrage_factoring_klause enthalten, aber die FROM-Unterabfrage kann die Unterabfrage_factoring_klause enthalten.

In Ihrem Fall haben Sie eine zufällige Funktion verwendet, die vom Optimierer anders behandelt wird, da er als Inline-Ansicht und nicht als materialisierte Perspektive behandelt wird. Als @ ibre5041 vorgeschlagen, verwenden Sie eine EXPLAIN PLAN für verschiedene Fälle.

Betrachten Sie den Fall eines rekursiven CTE, es wird intern jedes Mal verwendet.

WITH generator (value) AS (
    SELECT 1 FROM DUAL 
UNION ALL 
    SELECT value + 1 
    FROM generator 
    WHERE value < 10 
) 
SELECT value 
FROM generator; 

Plan hash value: 1492144221 

-------------------------------------------------------------------------------------------------- 
| Id | Operation         | Name | Rows | Bytes | Cost (%CPU)| Time  | 
-------------------------------------------------------------------------------------------------- 
| 0 | SELECT STATEMENT       |  |  2 | 26 |  4 (0)| 00:00:01 | 
| 1 | VIEW          |  |  2 | 26 |  4 (0)| 00:00:01 | 
| 2 | UNION ALL (RECURSIVE WITH) BREADTH FIRST|  |  |  |   |   | 
| 3 | FAST DUAL        |  |  1 |  |  2 (0)| 00:00:01 | 
|* 4 | RECURSIVE WITH PUMP     |  |  |  |   |   | 
-------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 

    4 - filter("VALUE"<10) 
1

Mindestens in Teradata es wie erwartet funktioniert, wird der Zufallswert nur einmal berechnet:

With Test as(
    select random(1,1000000) as x --pseudo code 
) 
select '1st select', x from Test 
union 
select '2nd select', x from Test 
; 

*** Query completed. 2 rows found. 2 columns returned. 
*** Total elapsed time was 1 second. 

'1st select'   x 
------------ ----------- 
1st select   422654 
2nd select   422654 
+0

Ich würde nicht sagen "wie erwartet" - die Erwartungen sind für verschiedene Menschen unterschiedlich. Ich habe immer nur mit Oracle gearbeitet. Ich lese die Dokumentation für das Unterabfrage-Factoring, und was ich "erwarte", ist in diesem Fall, unterschiedliche Werte zu erhalten, denn das ist das angegebene, dokumentierte Verhalten des CTE in diesem Fall (in Oracle). – mathguy

+0

@mathguy: Deshalb gibt es Standards und basierend auf Standard-SQL muss ein * Common Table Expression * die gleichen Daten zurückgeben, wenn mehrfach darauf zugegriffen wird. – dnoeth

+0

Wenn ein Anbieter die Standards ignoriert (wie es ihnen freisteht), und sie dies dokumentieren, dann "erwarte" ich immer noch, dass sich eine Funktion so verhält, wie vom Hersteller dokumentiert, nicht wie es der Standard sagt. In Oracle erwarte ich 'null || 'x'', um' 'x'' zurückzugeben, wenn ich ganz genau weiß, benötigt der Standard' null '. Ich stimme dem Standard zu, und ich finde Oracles Entscheidungen merkwürdig (oder manchmal geradezu dumm), aber ich erwarte immer noch, dass das Verhalten das ist, was Oracle sagt, wenn ich sein Produkt benutze. – mathguy