2016-07-18 22 views
1

Ich habe zwei CTEs, der erste führt ein 'Select Top N', um eine Probe von Datensätzen zu erhalten, dann teilt die zweite jede Aufzeichnung, die 2 Zeilen für jeden Datensatz produziert.Warum wird CTE zweimal ausgeführt?

Ich habe mit gefälschten Daten reproduziert, mit newid(), um die Sortierreihenfolge der Probe zu bestimmen.

declare @testtab table (Id bigint identity (1,1), ColA varchar(10), ColB varchar(10)) 
-- generate 1000 sample records 
declare @cnt int = 0 
while (@cnt<1000) 
begin 
    insert @testtab(ColA, ColB) values ('A'+convert(varchar,@cnt), 'B'+convert(varchar,@cnt)) 
    set @cnt+=1 
end 

;with SampleRecs as(
select top 1 * from @testtab order by newid() 
) 
,SplitRecs as (
select 0 Pos,Id,ColA Col from SampleRecs 
union all 
select 1, id, ColB col from SampleRecs 
) 
select * from SplitRecs 
order by id, pos 

Das erwartete Ergebnis ist zwei Zeilen für jede Eingabezeile ist so etwas wie:

Pos Id Col 
0 720 A719 
1 720 A719 

Allerdings habe ich bekommen, was ist so etwas wie

Pos Id Col 
0 720 A719 
1 774 B773 

So die "top 1" in CTE scheint zweimal in der CTE2 ausgeführt werden.

Ist das normal? Kann mir jemand auf irgendeine Dokumentation hinweisen, die dieses Verhalten erklären kann?

+1

weil der CTE Sie die Auswahl sind splitrecs ist, die alle eine UNION hat so dauert es eine Top 1 dann Gewerkschaften anohter Top 1 und Sie erhalten 2 Ergebnisse – Matt

Antwort

3

Dies ist, weil Sie die erste CTE zweimal in der zweiten CTE über die referenzieren.

CTE s sind im Wesentlichen temporäre Ansichten, die nach dem Aufruf verworfen werden. So wird jeder Anruf an die erste CTE die SELECT Anweisung separat ausführen, und da Ihre ORDER BY über NEWID() ist, können Sie erwarten, ein anderes Ergebnis 10 für beide Ausführungen zu erhalten.

Ersetzen der CTE Verweis in der Abfrage mit der eigentlichen Aussage hilft zu veranschaulichen, was passiert:

SampleRecs ist definiert als:

wählen top 1 * aus @testtab Auftrag von newid()

So alle Verweise mit diesem Teil Abfrage ersetzt, geben Sie folgendes:

;With SplitRecs As 
(
    Select 0  As Pos, 
      Id, 
      ColA As Col 
    From (Select Top 1 * From @testtab Order By NewId()) As A 
    Union All 
    Select 1  As Pos, 
      Id, 
      ColB As Col 
    From (Select Top 1 * From @testtab Order By NewId()) As B 
) 
Select * 
From SplitRecs 
Order By Id, pos 

Sie erhalten das gleiche Verhalten wie bei der Variante CTE.

Um die Ergebnisse zu erhalten, die Sie erwarten, können Sie die folgenden, stattdessen tun, der den Pos Wert auf Saatgut eine CROSS JOIN nutzt:

Select  X.Pos As Pos, 
      Id, 
      ColA As Col 
From  (Select Top 1 * From @testtab Order By NewId()) As A 
Cross Join (Select 0 As Pos Union All Select 1) As X 
Order By X.Pos 
+0

Ist dies nicht machen CTEs dann sehr ineffizient? Praktisch, ja, aber ineffizient. Lieber die Mühe machen, temporäre Tabellen zu erstellen und dann zu löschen? Von der Art und Weise, wie sie in verschiedenen Artikeln beschrieben wurden, hatte ich immer den Eindruck, dass CTEs äquivalent zu Temp-Tabellen sind, aber dies ist ein sehr signifikanter Unterschied. – user6604437