ROW_NUMBER () sem PARTITION BY ainda gera o iterador de segmento

11

Estou escrevendo em uma publicação futura do meu blog sobre funções de janela agregadas e de classificação, especificamente os iteradores do Projeto de Segmento e Sequência. Pelo que entendi, o segmento identifica linhas em um fluxo que constitui o fim / início de um grupo, portanto, a seguinte consulta:

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

Usará o segmento para saber quando uma linha pertence a um grupo diferente da linha anterior. O iterador do Sequence Project faz o cálculo do número da linha real, com base na saída da saída do iterador do segmento.

Mas a consulta a seguir, usando essa lógica, não deve incluir um segmento, porque não há expressão de partição.

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

No entanto, quando tento essa hipótese, essas duas consultas usam um operador de segmento. A única diferença é que a segunda consulta não precisa de um GroupByno segmento. Isso não elimina a necessidade de um segmento em primeiro lugar?

Exemplo

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
fonte
1
Embora não exista expressão de partição, acho que você ainda está dividindo tecnicamente o conjunto de resultados em partições, embora apenas uma neste caso?
MarkSinkinson
O QP mostra um vazio, de <GroupBy />modo que o segmento realmente não faz nada, quase, produz a coluna do segmento para o operador Sequence Project. A razão para o operador do segmento estar lá pode ser que o operador do Projeto de Sequência precise desse valor para realizar seu trabalho.
Mikael Eriksson
Essa é a minha teoria também. Mas o otimizador geralmente elimina esses tipos de operadores
desnecessários

Respostas:

12

Encontrei este post de 6 anos mencionando o mesmo comportamento.

Parece que ROW_NUMBER()sempre inclui um operador de segmento, PARTITION BYusado ou não. Se eu tivesse que adivinhar, diria que isso ocorre porque facilita a criação de um plano de consulta no mecanismo.

Se o segmento é necessário na maioria dos casos, e nos casos em que não é necessário, é essencialmente uma não operação de custo zero, é muito mais simples incluí-lo sempre no plano quando uma função de janelas é usada.

JNK
fonte
11

De acordo com showplan.xsd para o plano de execução, GroupByaparece sem minOccursou maxOccursatributos que, por conseguinte, são padronizados para [1..1] tornar o elemento obrigatório, não necessariamente conteúdo. O elemento filho ColumnReferencedo tipo ( ColumnReferenceType) possui minOccurs0 e maxOccursilimitado [0 .. *], tornando-o opcional , portanto, o elemento vazio permitido. Se você tentar remover manualmente GroupBye forçar o plano, receberá o erro esperado:

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

Curiosamente, descobri que você pode remover manualmente o operador Segment para obter um plano válido para forçar que se parece com isso:

insira a descrição da imagem aqui

No entanto, quando você executa esse plano (usando OPTION ( USE PLAN ... )), o Operador de segmento reaparece magicamente. Apenas mostra que o otimizador leva apenas os planos XML como um guia aproximado.

Meu equipamento de teste:

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

Recorte o plano XML da plataforma de teste e salve-o como um .sqlplan para visualizar o plano menos o Segmento.

PS: Eu não gastaria muito tempo analisando os planos do SQL manualmente, como se você me conhecesse, saberia que considero isso um trabalho ocupado e demorado, e algo que eu nunca faria. Oh, espere !? :)

wBob
fonte
Você tem muito tempo em suas mãos ... Bom trabalho!
Mark Sinkinson
Concordo com Mark. Estou aprendendo coisas que nem pensei em pedir. Obrigado! :)
Daniel Hutmacher