私は、ランク付けおよび集計ウィンドウ関数、特にセグメントおよびシーケンスプロジェクトイテレータに関する私のブログ投稿を書いています。私が理解している方法は、セグメントがグループの終了/開始を構成するストリーム内の行を識別するため、次のクエリです。
SELECT ROW_NUMBER() OVER (PARTITION BY someGroup ORDER BY someOrder)
セグメントを使用して、行が前の行以外の別のグループに属していることを通知します。次に、Sequence Projectイテレーターは、Segmentイテレーターの出力に基づいて、実際の行番号を計算します。
ただし、そのロジックを使用する次のクエリでは、パーティション式がないため、セグメントを含める必要はありません。
SELECT ROW_NUMBER() OVER (ORDER BY someGroup, someOrder)
ただし、この仮説を試すと、これらのクエリは両方ともセグメント演算子を使用します。唯一の違いは、2番目のクエリではセグメントに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;
同じ動作について言及しているこの6年前のブログ投稿を見つけました。
ROW_NUMBER()
は、PARTITION BY
が使用されているかどうか。推測しなければならないのは、エンジンでクエリプランを簡単に作成できるためです。
セグメントがほとんどの場合に必要であり、それが不要な場合、それは本質的にゼロコストの非演算であり、ウィンドウ関数が使用されているときは常にそれを計画に含めるだけの方がはるかに簡単です。
実行プランの showplan.xsd によると、GroupBy
はminOccurs
またはmaxOccurs
属性なしで表示されるため、デフォルトで[1..1]になります。要素を必ずしもコンテンツにする必要はありません。タイプ(ColumnReference
)の子要素ColumnReferenceType
にはminOccurs
0とmaxOccurs
unbounded [0 .. *]があり、optional、したがって、許可された空の要素。 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)
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プランを手動で切り刻むのにあまり時間をかけないでください。まるで、私がそれを時間を浪費していると見なしていることを知っているかのように 忙しい作業 と私は絶対にしないものです。ちょっと待って!? :)