ROW_NUMBER () без PARTITION BY по-прежнему генерирует итератор сегмента

11

Я пишу в своем следующем блоге о ранжировании и агрегировании оконных функций, в частности, об итераторах Segment и Sequence Project. Насколько я понимаю, Segment идентифицирует строки в потоке, которые составляют конец / начало группы, поэтому следующий запрос:

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

Сегмент будет использовать, чтобы указать, когда строка принадлежит другой группе, отличной от предыдущей строки. Затем итератор проекта последовательности выполняет фактическое вычисление номера строки на основе выходных данных итератора сегмента.

Но следующий запрос, использующий эту логику, не должен включать сегмент, потому что нет выражения раздела.

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

Однако, когда я попробую эту гипотезу, оба этих запроса используют оператор Сегмент. Единственное отличие состоит в том, что второй запрос не нуждается GroupByв сегменте. Разве это не устраняет необходимость сегмента в первую очередь?

пример

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
Хотя выражения для разделов нет, я полагаю, что вы все еще технически разбиваете результирующий набор на разделы, хотя в данном случае только один?
Марк Синкинсон
QP показывает пустое значение, <GroupBy />поэтому сегмент действительно ничего не делает, почти он выводит столбец сегмента оператору проекта последовательности. Причиной присутствия оператора сегмента может быть то, что оператору проекта последовательности необходимо это значение для выполнения своей работы.
Микаэль Эрикссон
Это тоже моя теория. Но оптимизатор обычно избавляется от таких ненужных операторов, имхо ..
Даниэль Хутмахер

Ответы:

12

Я нашел это 6-летнее сообщение в блоге, упоминающее то же самое поведение.

Похоже, ROW_NUMBER()всегда включает в себя оператор сегмента, независимо от того PARTITION BY, используется он или нет. Если бы мне пришлось угадывать, я бы сказал, что это потому, что это облегчает создание плана запросов на движке.

Если сегмент нужен в большинстве случаев, а в тех случаях, когда он не нужен, то это, по сути, неоперация с нулевой стоимостью, гораздо проще просто всегда включать его в план при использовании оконной функции.

JNK
источник
11

В соответствии с showplan.xsd для плана выполнения, GroupByотображается без атрибутов minOccursили maxOccursатрибутов, поэтому по умолчанию используется значение [1..1], что делает элемент обязательным, а не обязательно содержимым. Дочерний элемент ColumnReferenceтипа ( ColumnReferenceType) имеет minOccurs0 и maxOccursнеограниченный [0 .. *], что делает его необязательным , следовательно, допускается пустой элемент. Если вы вручную попытаетесь удалить GroupByи принудительно ввести план, вы получите ожидаемую ошибку:

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

Интересно, что я обнаружил, что вы можете вручную удалить оператор Сегмент, чтобы получить действительный план форсирования, который выглядит следующим образом:

введите описание изображения здесь

Однако, когда вы работаете с этим планом (используя OPTION ( USE PLAN ... )), Оператор Сегмента волшебным образом появляется снова. Просто показывает, что оптимизатор воспринимает планы XML только как грубое руководство.

Мой испытательный стенд:

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

Извлеките план XML из испытательного стенда и сохраните его как .sqlplan, чтобы просмотреть план без сегмента.

PS Я бы не стал тратить слишком много времени на ручное планирование SQL-планов, так как если бы вы знали меня, вы бы поняли, что я расцениваю это как занятое временем занятие и то, чего я никогда бы не сделал. Ох, подожди !? :)

wBob
источник
У тебя слишком много времени на руках ... Отличная работа!
Марк Синкинсон
Согласен с Марком. Я учу вещи, о которых даже не думал просить. Спасибо! :)
Даниэль Хутмахер