ROW_NUMBER () tanpa PARTITION BY masih menghasilkan iterator Segmen

11

Saya menulis di posting blog saya yang akan datang tentang fungsi peringkat dan jendela agregat, khususnya iterator Segment and Sequence Project. Cara saya memahaminya adalah bahwa Segmen mengidentifikasi baris dalam aliran yang merupakan akhir / awal grup, jadi pertanyaan berikut:

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

Akan menggunakan Segmen untuk memberi tahu kapan baris milik grup lain selain dari baris sebelumnya. Iterator Proyek Urutan kemudian melakukan perhitungan angka baris aktual, berdasarkan output dari output iterator Segmen.

Tetapi kueri berikut, menggunakan logika itu, tidak harus menyertakan Segmen, karena tidak ada ekspresi partisi.

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

Namun, ketika saya mencoba hipotesis ini, kedua kueri ini menggunakan operator Segmen. Satu-satunya perbedaan adalah bahwa permintaan kedua tidak perlu GroupBypada Segmen. Bukankah itu menghilangkan kebutuhan untuk Segmen di tempat pertama?

Contoh

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
sumber
1
Meskipun tidak ada ekspresi partisi, saya kira Anda masih secara teknis membagi hasil yang ditetapkan menjadi partisi, meskipun hanya satu dalam hal ini?
Mark Sinkinson
QP menunjukkan kosong <GroupBy />sehingga segmen benar-benar tidak melakukan apa-apa, hampir, ia mengeluarkan kolom segmen ke operator Proyek Urutan. Alasan bagi operator segmen untuk berada di sana mungkin karena operator Proyek Urutan membutuhkan nilai tersebut untuk melakukan tugasnya.
Mikael Eriksson
Itu teoriku juga. Tapi pengoptimal biasanya menghilangkan operator yang tidak perlu ini, ya ..
Daniel Hutmacher

Jawaban:

12

Saya menemukan posting blog berusia 6 tahun ini menyebutkan perilaku yang sama.

Sepertinya ROW_NUMBER()selalu menyertakan operator segmen, apakah PARTITION BYdigunakan atau tidak. Jika saya harus menebak saya akan mengatakan ini karena itu membuat membuat rencana permintaan lebih mudah di mesin.

Jika segmen diperlukan dalam kebanyakan kasus, dan dalam kasus di mana tidak diperlukan pada dasarnya itu adalah non-operasi tanpa biaya, itu jauh lebih mudah untuk selalu memasukkannya ke dalam rencana ketika fungsi windowing digunakan.

JNK
sumber
11

Menurut showplan.xsd untuk rencana eksekusi, GroupBymuncul tanpa minOccursatau maxOccursatribut yang karenanya standar untuk [1..1] membuat elemen wajib, tidak harus konten. Elemen turunan ColumnReferencedari type ( ColumnReferenceType) memiliki minOccurs0 dan tidak maxOccursterikat [0 .. *], menjadikannya opsional , karenanya elemen kosong yang diizinkan. Jika Anda mencoba untuk menghapus GroupBydan memaksa paket secara manual, Anda mendapatkan kesalahan yang diharapkan:

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

Menariknya saya menemukan Anda dapat secara manual menghapus operator Segmen untuk mendapatkan rencana pemaksaan yang berlaku yang terlihat seperti ini:

masukkan deskripsi gambar di sini

Namun ketika Anda menjalankan dengan rencana itu (menggunakan OPTION ( USE PLAN ... )) Operator Segmen muncul kembali secara ajaib. Hanya untuk menunjukkan pengoptimal hanya mengambil rencana XML sebagai panduan kasar.

Rig pengujian saya:

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

Memotong rencana XML dari rig uji dan menyimpannya sebagai .sqlplan untuk melihat paket minus Segmen.

PS Saya tidak akan menghabiskan terlalu banyak waktu memotong-motong rencana SQL secara manual seolah-olah Anda tahu saya, Anda akan tahu saya menganggapnya sebagai pekerjaan yang sibuk memakan waktu dan sesuatu yang tidak akan pernah saya lakukan. Oh, tunggu !? :)

wBob
sumber
Anda memiliki terlalu banyak waktu di tangan Anda ... Kerja bagus!
Mark Sinkinson
Setuju dengan Markus. Saya belajar hal-hal yang saya bahkan tidak berpikir untuk meminta. Terima kasih! :)
Daniel Hutmacher