ROW_NUMBER () senza PARTITION BY genera ancora iteratore di segmento


11

Sto scrivendo su un mio prossimo post sul blog sulle funzioni di classifica e finestra aggregata, in particolare gli iteratori del Segment and Sequence Project. Il modo in cui lo capisco è che il segmento identifica le righe in un flusso che costituiscono la fine / inizio di un gruppo, quindi la seguente query:

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

Utilizzerà il segmento per indicare quando una riga appartiene a un gruppo diverso da quello precedente. L'iteratore del Progetto sequenza esegue quindi il calcolo del numero di riga effettivo, in base all'output dell'output dell'iteratore del segmento.

Ma la query seguente, utilizzando quella logica, non dovrebbe includere un segmento, poiché non esiste un'espressione di partizione.

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

Tuttavia, quando provo questa ipotesi, entrambe queste query utilizzano un operatore Segmento. L'unica differenza è che la seconda query non ha bisogno di una GroupBysul segmento. Questo non elimina in primo luogo la necessità di un segmento?

Esempio

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;

1
Anche se non esiste un'espressione di partizione, suppongo che tu stia ancora tecnicamente suddividendo il set di risultati in partizioni, anche se solo in questo caso?
Mark Sinkinson,

Il QP mostra uno spazio vuoto, <GroupBy />quindi il segmento non fa davvero nulla, quasi, invia la colonna del segmento all'operatore Progetto sequenza. La ragione per la presenza dell'operatore del segmento potrebbe essere che l'operatore del Progetto sequenza ha bisogno di quel valore per svolgere il proprio lavoro.
Mikael Eriksson,

Questa è anche la mia teoria. Ma l'ottimizzatore di solito elimina questo tipo di operatori inutili, imho ..
Daniel Hutmacher,

Risposte:


12

Ho trovato questo post di 6 anni che menziona lo stesso comportamento.

Sembra che ROW_NUMBER()includa sempre un operatore di segmento, PARTITION BYutilizzato o meno. Se dovessi indovinare, direi che è perché rende più semplice la creazione di un piano di query sul motore.

Se il segmento è necessario nella maggior parte dei casi e nei casi in cui non è necessario, si tratta essenzialmente di una non-operazione a costo zero, è molto più semplice includerlo sempre nel piano quando viene utilizzata una funzione di windowing.


11

Secondo showplan.xsd per il piano di esecuzione, GroupByappare senza minOccurso con maxOccursattributi che quindi diventano predefiniti a [1..1] rendendo l'elemento obbligatorio, non necessariamente contenuto. L'elemento figlio ColumnReferencedi tipo ( ColumnReferenceType) ha minOccurs0 e maxOccursillimitato [0 .. *], rendendolo facoltativo , quindi l'elemento vuoto consentito. Se si tenta di rimuovere manualmente GroupBye forzare il piano si ottiene l'errore previsto:

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

È interessante notare che ho scoperto che puoi rimuovere manualmente l'operatore Segmento per ottenere un piano valido per forzare che assomiglia a questo:

inserisci qui la descrizione dell'immagine

Tuttavia, quando corri con quel piano (usando OPTION ( USE PLAN ... )), il Segment Operator riappare magicamente. Va solo a mostrare che l'ottimizzatore prende solo i piani XML come una guida approssimativa.

Il mio banco prova:

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

Taglia il piano XML dal banco prova e salvalo come piano .sql per visualizzare il piano meno il segmento.

PS Non passerei troppo tempo a tagliare manualmente i piani SQL come se mi conoscessi sapresti che lo considero un lavoro impegnativo che richiede tempo e qualcosa che non farei mai. Oh aspetta !? :)


Hai troppo tempo a disposizione ... Bel lavoro!
Mark Sinkinson,

Sono d'accordo con Mark. Sto imparando cose che non pensavo nemmeno di chiedere. Grazie! :)
Daniel Hutmacher il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.