ROW_NUMBER () без PARTITION BY все ще генерує ітератор сегмента


11

Я пишу в майбутній публікації свого блогу про функції ранжування та сукупності вікон, зокрема ітераторів проекту сегмента та послідовності. Як я розумію, це те, що сегмент ідентифікує рядки в потоці, які складають кінець / початок групи, тож наступний запит:

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 />тому сегмент насправді нічого не робить, він майже виводить стовпець сегмента оператору Sequence Project. Причиною виникнення оператора сегменту може бути те, що оператор проекту послідовності потребує цього значення для виконання своєї роботи.
Мікаель Ерікссон

Це теж моя теорія. Але оптимізатор, як правило,
усуває

Відповіді:


12

Я знайшов цю 6-річну публікацію в блозі, де згадується та ж поведінка.

Схоже, ROW_NUMBER()завжди входить оператор сегмента, PARTITION BYвикористовується він чи ні. Якби мені довелося здогадатися, я б сказав, що це тому, що це спрощує створення плану запитів на двигуні.

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


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-планів вручну, як ніби ви знаєте мене, ви б знали, що я вважаю це зайнятою роботою часом і тим, що я ніколи б не зробив. Ой чекай !? :)


У вас занадто багато часу на руках ... Гарна робота!
Марк Сінкінсон

Погодьтеся з Марком. Я вивчаю речі, про які навіть не думав просити. Спасибі! :)
Даніель Хатмахер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.