ROW_NUMBER () ohne PARTITION BY generiert weiterhin einen Segmentiterator

11

Ich schreibe in einem meiner nächsten Blog-Beiträge über Ranking- und aggregierte Fensterfunktionen, insbesondere die Segment- und Sequenzprojekt-Iteratoren. Ich verstehe es so, dass Segment Zeilen in einem Stream identifiziert, die das Ende / den Anfang einer Gruppe bilden, also die folgende Abfrage:

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

Verwendet Segment, um festzustellen, wann eine Zeile zu einer anderen Gruppe als der vorherigen Zeile gehört. Der Sequenzprojekt-Iterator führt dann die tatsächliche Zeilennummernberechnung basierend auf der Ausgabe der Ausgabe des Segmentiterators durch.

Die folgende Abfrage, die diese Logik verwendet, sollte jedoch kein Segment enthalten müssen, da kein Partitionsausdruck vorhanden ist.

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

Wenn ich diese Hypothese versuche, verwenden diese beiden Abfragen jedoch einen Segmentoperator. Der einzige Unterschied besteht darin, dass für die zweite Abfrage kein GroupBySegment benötigt wird. Beseitigt das nicht in erster Linie die Notwendigkeit eines Segments?

Beispiel

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
quelle
1
Obwohl es keinen Partitionsausdruck gibt, teilen Sie die Ergebnismenge vermutlich technisch immer noch in Partitionen auf, wenn auch in diesem Fall nur eine?
Mark Sinkinson
Der QP zeigt ein Leerzeichen an, <GroupBy />sodass das Segment wirklich nichts tut. Fast gibt es die Segmentspalte an den Operator "Sequenzprojekt" aus. Der Grund dafür, dass der Segmentoperator dort ist, könnte sein, dass der Sequenzprojektoperator diesen Wert benötigt, um seine Arbeit zu erledigen.
Mikael Eriksson
Das ist auch meine Theorie. Aber der Optimierer beseitigt normalerweise diese Art von unnötigen Operatoren, imho ..
Daniel Hutmacher

Antworten:

12

Ich fand diesen 6 Jahre alten Blog-Beitrag, der das gleiche Verhalten erwähnte.

Es sieht so aus ROW_NUMBER(), als ob immer ein Segmentoperator enthalten ist, unabhängig davon, ob er PARTITION BYverwendet wird oder nicht. Wenn ich raten müsste, würde ich sagen, dass dies daran liegt, dass das Erstellen eines Abfrageplans für die Engine einfacher ist.

Wenn das Segment in den meisten Fällen benötigt wird und in den Fällen, in denen es nicht benötigt wird, im Wesentlichen eine kostengünstige Nichtoperation ist, ist es viel einfacher, es einfach immer in den Plan aufzunehmen, wenn eine Fensterfunktion verwendet wird.

JNK
quelle
11

Gemäß showplan.xsd für den Ausführungsplan wird GroupByohne minOccursoder maxOccursAttribute angezeigt, die daher standardmäßig [1..1] verwenden, wodurch das Element obligatorisch und nicht unbedingt inhaltlich ist. Das untergeordnete Element ColumnReferencevom Typ ( ColumnReferenceType) hat minOccurs0 und ist maxOccursunbegrenzt [0 .. *], was es optional macht , daher das zulässige leere Element. Wenn Sie manuell versuchen, GroupByden Plan zu entfernen und zu erzwingen, wird der erwartete Fehler angezeigt:

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].

Interessanterweise habe ich festgestellt, dass Sie den Segmentoperator manuell entfernen können, um einen gültigen Plan für das Forcen zu erhalten, der folgendermaßen aussieht:

Geben Sie hier die Bildbeschreibung ein

Wenn Sie jedoch mit diesem Plan (unter Verwendung OPTION ( USE PLAN ... )) ausgeführt werden, wird der Segmentoperator auf magische Weise wieder angezeigt. Nur um zu zeigen, dass der Optimierer nur die XML-Pläne als grobe Richtlinie verwendet.

Mein Prüfstand:

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

Hacken Sie den XML-Plan vom Prüfstand aus und speichern Sie ihn als .sqlplan, um den Plan abzüglich des Segments anzuzeigen.

PS Ich würde nicht zu viel Zeit damit verbringen, SQL-Pläne manuell zu durchforsten, als ob Sie mich kennen würden. Sie würden wissen, dass ich es als zeitaufwändige Arbeit betrachte und etwas, das ich niemals tun würde. Oh, warte!? :)

wBob
quelle
Sie haben viel zu viel Zeit ... Gute Arbeit!
Mark Sinkinson
Stimme Mark zu. Ich lerne Dinge, nach denen ich nicht einmal gefragt habe. Vielen Dank! :)
Daniel Hutmacher