it-swarm.com.de

ROW_NUMBER () ohne PARTITION BY generiert weiterhin einen Segmentiterator

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 die zweite Abfrage kein GroupBy für das Segment benötigt. 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;
11

Ich fand diesen 6 Jahre alten Blog-Beitrag, in dem das gleiche Verhalten erwähnt wurde.

Es sieht so aus, als ob ROW_NUMBER() immer einen Segmentoperator enthält, unabhängig davon, ob PARTITION BY wird verwendet 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.

12
JNK

Gemäß showplan.xsd für den Ausführungsplan wird GroupBy ohne die Attribute minOccurs oder maxOccurs angezeigt, die daher standardmäßig [1..1] sind. das Element obligatorisch machen, nicht unbedingt zufrieden. Das untergeordnete Element ColumnReference vom Typ (ColumnReferenceType) hat minOccurs 0 und maxOccurs unbegrenzt [0 .. *], wodurch es optional , daher das erlaubte leere Element. Wenn Sie manuell versuchen, GroupBy zu entfernen und den Plan 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:

enter image description here

Wenn Sie jedoch mit diesem Plan ausführen (mit OPTION ( USE PLAN ... )), 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 durchsuchen, als ob Sie mich kennen würden. Sie würden wissen, dass ich es als zeitaufwändig betrachte beschäftigte Arbeit und etwas, das ich niemals tun würde. Oh, warte!? :)

11
wBob