ROW_NUMBER () không có PHẦN THAM GIA BỞI vẫn tạo Trình lặp phân đoạn


11

Tôi đang viết trên một bài đăng blog sắp tới của tôi về xếp hạng và tổng hợp các chức năng của cửa sổ, cụ thể là các trình lặp của Dự án Phân đoạn và Trình tự. Theo cách hiểu của tôi, đó là Phân đoạn xác định các hàng trong luồng tạo thành kết thúc / bắt đầu của một nhóm, do đó, truy vấn sau:

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

Sẽ sử dụng Phân đoạn để biết khi nào một hàng thuộc về một nhóm khác ngoài hàng trước đó. Trình lặp dự án tuần tự sau đó thực hiện tính toán số hàng thực tế, dựa trên đầu ra của đầu ra của trình lặp phân đoạn.

Nhưng truy vấn sau, sử dụng logic đó, không cần phải bao gồm Phân đoạn, vì không có biểu thức phân vùng.

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

Tuy nhiên, khi tôi thử giả thuyết này, cả hai truy vấn này đều sử dụng toán tử Segment. Sự khác biệt duy nhất là truy vấn thứ hai không cần một GroupByPhân đoạn. Điều đó không loại bỏ sự cần thiết của Phân khúc ở nơi đầu tiên?

Thí dụ

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
Mặc dù không có biểu thức phân vùng, tôi đoán bạn vẫn đang phân chia kết quả được thiết lập thành các phân vùng, mặc dù chỉ có một trong trường hợp này?
Mark Sinkinson

QP hiển thị trống <GroupBy />nên phân đoạn thực sự không làm gì cả, hầu như, nó xuất cột phân đoạn cho toán tử Sequence Project. Lý do cho toán tử phân đoạn ở đó có thể là toán tử Sequence Project cần giá trị đó để thực hiện công việc của nó.
Mikael Eriksson

Đó cũng là lý thuyết của tôi. Nhưng trình tối ưu hóa thường loại bỏ các loại toán tử không cần thiết này, imho ..
Daniel Hutmacher

Câu trả lời:


12

Tôi tìm thấy bài viết blog 6 tuổi này đề cập đến hành vi tương tự.

Nó giống như ROW_NUMBER() luôn bao gồm một toán tử phân đoạn, cho dù PARTITION BYđược sử dụng hay không. Nếu tôi phải đoán tôi sẽ nói điều này là bởi vì nó làm cho việc tạo một kế hoạch truy vấn dễ dàng hơn trên công cụ.

Nếu hầu hết các phân đoạn là cần thiết và trong các trường hợp không cần thiết thì về cơ bản là không hoạt động với chi phí bằng 0, sẽ đơn giản hơn rất nhiều khi luôn đưa nó vào kế hoạch khi sử dụng chức năng cửa sổ.


11

Theo showplan.xsd cho kế hoạch thực hiện, GroupByxuất hiện mà không có minOccurshoặc maxOccurscác thuộc tính do đó mặc định là [1..1] làm cho phần tử bắt buộc, không nhất thiết phải là nội dung. Phần tử con ColumnReferencecủa loại (ColumnReferenceType ) có minOccurs0 và maxOccurskhông bị ràng buộc [0 .. *], làm cho nó là tùy chọn , do đó phần tử trống được phép. Nếu bạn cố gắng xóa thủ công GroupByvà buộc gói bạn gặp lỗi dự kiến:

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

Thật thú vị tôi thấy bạn có thể xóa thủ công Phân đoạn để có được một gói hợp lệ để buộc nó trông như thế này:

nhập mô tả hình ảnh ở đây

Tuy nhiên khi bạn chạy với kế hoạch đó (sử dụng OPTION ( USE PLAN ... ) ) Toán tử phân đoạn sẽ xuất hiện lại một cách kỳ diệu. Chỉ cần hiển thị trình tối ưu hóa chỉ lấy các kế hoạch XML làm hướng dẫn sơ bộ.

Giàn kiểm tra của tôi:

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

Cắt bỏ kế hoạch XML từ thiết bị thử nghiệm và lưu nó dưới dạng .sqlplan để xem kế hoạch trừ Phân đoạn.

Tái bút: Tôi sẽ không dành quá nhiều thời gian để cắt xén các kế hoạch SQL theo cách thủ công vì nếu bạn biết tôi, bạn sẽ biết tôi coi đó là công việc bận rộn ăn thời gian và là điều tôi sẽ không bao giờ làm. Ôi chao!? :)


Bạn có quá nhiều thời gian trên tay ... Làm tốt lắm!
Mark Sinkinson

Đồng ý với Mark. Tôi đang học những thứ mà tôi thậm chí không nghĩ để yêu cầu. Cảm ơn! :)
Daniel Hutmacher
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.