ROW_NUMBER () bez PARTITION BY nadal generuje iterator segmentów

11

Piszę na jednym z moich nadchodzących postów na blogu o funkcjach okna rankingu i agregacji, w szczególności iteratorach projektu Segment i Sekwencja. Rozumiem, że Segment identyfikuje wiersze w strumieniu, które stanowią koniec / początek grupy, więc następujące zapytanie:

SELECT ROW_NUMBER() OVER (PARTITION BY someGroup ORDER BY someOrder)

Użyje Segmentu, aby określić, kiedy wiersz należy do innej grupy niż poprzedni. Następnie iterator projektu sekwencji wykonuje obliczenie rzeczywistej liczby wierszy na podstawie danych wyjściowych z iteratora segmentu.

Ale poniższe zapytanie, wykorzystujące tę logikę, nie powinno zawierać segmentu, ponieważ nie ma wyrażenia partycji.

SELECT ROW_NUMBER() OVER (ORDER BY someGroup, someOrder)

Jednak gdy wypróbuję tę hipotezę, oba te zapytania wykorzystują operator segmentu. Jedyna różnica polega na tym, że drugie zapytanie nie wymaga GroupBysegmentu. Czy to nie eliminuje potrzeby segmentu?

Przykład

CREATE TABLE dbo.someTable (
    someGroup   int NOT NULL,
    someOrder   int NOT NULL,
    someValue   numeric(8, 2) NOT NULL,
    PRIMARY KEY CLUSTERED (someGroup, someOrder)
);

--- Query 1:
SELECT ROW_NUMBER() OVER (PARTITION BY someGroup ORDER BY someOrder)
FROM dbo.someTable;

--- Query 2:
SELECT ROW_NUMBER() OVER (ORDER BY someGroup, someOrder)
FROM dbo.someTable;
Daniel Hutmacher
źródło
1
Chociaż nie ma wyrażenia partycji, myślę, że nadal technicznie dzielisz zestaw wyników na partycje, choć w tym przypadku tylko jedna?
Mark Sinkinson,
QP pokazuje puste, <GroupBy />więc segment naprawdę nic nie robi, prawie wysyła kolumnę segmentu do operatora projektu sekwencji. Przyczyną obecności operatora segmentu może być to, że operator projektu sekwencji potrzebuje tej wartości do wykonania swojej pracy.
Mikael Eriksson,
To też moja teoria. Ale optymalizator zwykle
eliminuje

Odpowiedzi:

12

Znalazłem ten 6-letni wpis na blogu wspominający o tym samym zachowaniu.

Wygląda na to, że ROW_NUMBER()zawsze zawiera operator segmentu, niezależnie od tego, czy PARTITION BYjest używany, czy nie. Gdybym musiał zgadywać, powiedziałbym, że to dlatego, że ułatwia to tworzenie planu zapytań w silniku.

Jeśli segment jest potrzebny w większości przypadków, a w przypadkach, gdy nie jest potrzebny, jest to zasadniczo zerowy brak operacji, o wiele łatwiej jest po prostu zawsze włączyć go do planu, gdy używana jest funkcja okienkowania.

JNK
źródło
11

Zgodnie z showplan.xsd dla planu wykonania, GroupBypojawia się bez minOccurslub maxOccursatrybuty, które w związku z tym domyślnie [1..1] sprawiają, że element jest obowiązkowy, niekoniecznie treściowy. Element potomny ColumnReferencetype ( ColumnReferenceType) ma minOccurs0 i maxOccursnieograniczony [0 .. *], co czyni go opcjonalnym , stąd dozwolony pusty element. Jeśli ręcznie spróbujesz usunąć GroupByplan i wymusić go, otrzymasz oczekiwany błąd:

Msg 6965, Level 16, State 1, Line 29
XML Validation: Invalid content. Expected element(s): '{http://schemas.microsoft.com/sqlserver/2004/07/showplan}GroupBy','{http://schemas.microsoft.com/sqlserver/2004/07/showplan}DefinedValues','{http://schemas.microsoft.com/sqlserver/2004/07/showplan}InternalInfo'. Found: element '{http://schemas.microsoft.com/sqlserver/2004/07/showplan}SegmentColumn' instead. Location: /*:ShowPlanXML[1]/*:BatchSequence[1]/*:Batch[1]/*:Statements[1]/*:StmtSimple[1]/*:QueryPlan[1]/*:RelOp[1]/*:SequenceProject[1]/*:RelOp[1]/*:Segment[1]/*:SegmentColumn[1].

Co ciekawe, zauważyłem, że możesz ręcznie usunąć operatora segmentu, aby uzyskać prawidłowy plan wymuszania, który wygląda następująco:

wprowadź opis zdjęcia tutaj

Jednak gdy uruchomisz z tym planem (korzystasz OPTION ( USE PLAN ... )), Operator segmentu pojawi się magicznie. Po prostu pokazuje, że optymalizator przyjmuje jedynie plany XML jako przybliżony przewodnik.

Mój zestaw testowy:

USE tempdb
GO
SET NOCOUNT ON
GO
IF OBJECT_ID('dbo.someTable') IS NOT NULL DROP TABLE dbo.someTable
GO
CREATE TABLE dbo.someTable (
    someGroup   int NOT NULL,
    someOrder   int NOT NULL,
    someValue   numeric(8, 2) NOT NULL,
    PRIMARY KEY CLUSTERED (someGroup, someOrder)
);
GO

-- Generate some dummy data
;WITH cte AS (
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.someTable ( someGroup, someOrder, someValue )
SELECT rn % 333, rn % 444, rn % 55
FROM cte
GO


-- Try and force the plan
SELECT ROW_NUMBER() OVER (ORDER BY someGroup, someOrder)
FROM dbo.someTable
OPTION ( USE PLAN N'<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.2" Build="12.0.2000.8" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="1000" StatementId="1" StatementOptmLevel="TRIVIAL" CardinalityEstimationModelVersion="120" StatementSubTreeCost="0.00596348" StatementText="SELECT ROW_NUMBER() OVER (ORDER BY someGroup, someOrder)&#xD;&#xA;FROM dbo.someTable" StatementType="SELECT" QueryHash="0x193176312402B8E7" QueryPlanHash="0x77F1D72C455025A4" RetrievedFromCache="true">
          <StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
          <QueryPlan DegreeOfParallelism="1" CachedPlanSize="16" CompileTime="0" CompileCPU="0" CompileMemory="88">
            <OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="131072" EstimatedPagesCached="65536" EstimatedAvailableDegreeOfParallelism="4" />
            <RelOp AvgRowSize="15" EstimateCPU="8E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1000" LogicalOp="Compute Scalar" NodeId="0" Parallel="false" PhysicalOp="Sequence Project" EstimatedTotalSubtreeCost="0.00596348">
              <OutputList>
                <ColumnReference Column="Expr1002" />
              </OutputList>
              <SequenceProject>
                <DefinedValues>
                  <DefinedValue>
                    <ColumnReference Column="Expr1002" />
                    <ScalarOperator ScalarString="row_number">
                      <Sequence FunctionName="row_number" />
                    </ScalarOperator>
                  </DefinedValue>
                </DefinedValues>

                <!-- Segment operator completely removed from plan -->
                <!--<RelOp AvgRowSize="15" EstimateCPU="2E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1000" LogicalOp="Segment" NodeId="1" Parallel="false" PhysicalOp="Segment" EstimatedTotalSubtreeCost="0.00588348">
                  <OutputList>
                    <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Column="someGroup" />
                    <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Column="someOrder" />
                    <ColumnReference Column="Segment1003" />
                  </OutputList>
                  <Segment>
                    <GroupBy />
                    <SegmentColumn>
                      <ColumnReference Column="Segment1003" />
                    </SegmentColumn>-->


                    <RelOp AvgRowSize="15" EstimateCPU="0.001257" EstimateIO="0.00460648" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1000" LogicalOp="Clustered Index Scan" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Scan" EstimatedTotalSubtreeCost="0.00586348" TableCardinality="1000">
                      <OutputList>
                        <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Column="someGroup" />
                        <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Column="someOrder" />
                      </OutputList>
                      <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" ForceScan="false" NoExpandHint="false" Storage="RowStore">
                        <DefinedValues>
                          <DefinedValue>
                            <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Column="someGroup" />
                          </DefinedValue>
                          <DefinedValue>
                            <ColumnReference Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Column="someOrder" />
                          </DefinedValue>
                        </DefinedValues>
                        <Object Database="[tempdb]" Schema="[dbo]" Table="[someTable]" Index="[PK__someTabl__7CD03C8950FF62C1]" IndexKind="Clustered" Storage="RowStore" />
                      </IndexScan>
                    </RelOp>

                <!--</Segment>
                </RelOp>-->
              </SequenceProject>
            </RelOp>

          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>' )

Wytnij plan XML ze stanowiska testowego i zapisz go jako .sqlplan, aby wyświetlić plan bez Segmentu.

PS Nie spędzałbym zbyt wiele czasu na ręcznym omijaniu planów SQL, tak jakbyś mnie znał, wiedziałbyś, że uważam to za czasochłonną pracę i coś, czego nigdy nie zrobiłbym. Och, poczekaj !? :)

wBob
źródło
Masz zdecydowanie za dużo czasu ... Dobra robota!
Mark Sinkinson,
Zgadzam się z Markiem. Uczę się rzeczy, o które nawet nie pomyślałem. Dzięki! :)
Daniel Hutmacher,