ORDER 并按添加日期过滤,同时在行 ID 上使用 CLUSTERED IDENTITY PRIMARY KEY

ORDER and filter by add date while CLUSTERED IDENTITY PRIMARY KEY on row Id

我有一个 table (Orders) 和 CLUSTERED IDENTITY PRIMARY KEY (OrderId) 我正在 过滤 按添加日期列 (AddDate) 对数据进行排序。 有没有办法告诉查询优化器 AddDate 的排序方式与 OrderId 的排序方式相同(因此数据已经按 AddDate 排序)?

SQL 服务器真的不需要扫描整个 table 然后排序。所有操作需要扫描 table 直到找到结束日期,然后过滤掉开始日期之前的数据并 return 按原样(不排序)。

示例:

SELECT
      *
    FROM Orders
    WHERE AddDate BETWEEN @FromDate AND @ToDate
    ORDER BY AddDate

Is there a way to tell the query optimizer that the AddDate is ordered the same way the OrderId is (so the data is ordered by AddDate already)?

不,没有办法做到这一点。

但是您可以按 OrderId 而不是 AddDate 进行排序,如果 AddDate 的排序方式与 OrderId 相同,则 return同样的结果。但不幸的是,SQL 服务器无论如何都会扫描整个 table。

让我们获取 Northwind 订单 table 和 OrderDate 列。

查询:

SELECT *
FROM dbo.Orders
WHERE OrderDate BETWEEN '1997-12-10' AND '1998-03-05'
ORDER BY OrderDate

产生 this plan。它在应用过滤器时完全扫描聚簇索引,然后对结果进行排序。

查询:

SELECT *
FROM dbo.Orders
WHERE OrderDate BETWEEN '1997-12-10' AND '1997-12-17'
ORDER BY OrderId -- It's equivalent to ordering by OrderDate

产生 this plan。它还会完全扫描聚簇索引并应用过滤器,但它不会排序。

OrderDate 作为聚簇索引键将大大提高查询的性能,但您可能不希望拥有这样的聚簇索引键。然而,你云创建一个覆盖索引也将大大提高性能:

CREATE INDEX IX_Orders_OrderDate ON dbo.Orders(OrderDate)
INCLUDE ([OrderID], [CustomerID], [EmployeeID], [RequiredDate], [ShippedDate], [ShipVia], [Freight], [ShipName], [ShipAddress], [ShipCity], [ShipRegion], [ShipPostalCode], [ShipCountry])

查询:

SELECT *
FROM dbo.Orders
WHERE OrderDate BETWEEN '1997-12-10' AND '1998-03-05'
ORDER BY OrderDate

产生 this plan。它只是寻找索引。不能再快了。

但是那个索引很胖,它会惩罚数据修改。

但是,您可以利用如下更薄的索引:

CREATE INDEX IX_Orders_OrderDate ON dbo.Orders(OrderDate, OrderId)

使用这样的查询:

DECLARE @FromOrderId int, @ToOrderId int;
SELECT TOP (1) @FromOrderId = OrderId FROM dbo.Orders WHERE OrderDate <= '1997-12-10' ORDER BY OrderDate DESC, OrderId DESC;
SELECT TOP (1) @ToOrderId = OrderId FROM dbo.Orders WHERE OrderDate >= '1998-03-05' ORDER BY OrderDate ASC, OrderId ASC;

SELECT *
FROM dbo.Orders
WHERE 
    (OrderId  >= @FromOrderId OR @FromOrderId IS NULL)
    AND (OrderId <= @ToOrderId OR @ToOrderId IS NULL)
ORDER BY OrderID
OPTION (RECOMPILE)

它产生 this plan。只需要3次搜索就可以解决查询。

您无能为力。首先,如果您知道这是按相同方式排序的,那么就没有理由按 AddDate 而不是 OrderId 对结果集进行排序。 其次,如果您知道这种关系,您可以像@Jesus 向您展示的那样获得您所追求的日期范围的 OrderId 分配。 最好的方法是在 OrderDate 上创建额外的索引(仅)。对于这样一个简单的查询,这将是最好的,不需要包含额外的列,PK 无论如何都会在后台覆盖,并且将应用基于整个过滤。 您拥有的最后一个选项仍然会带来一些改进(不如索引,但仍然有帮助)是在 OrderDate 列上添加额外的统计信息,SQL 服务器 kardionality 估计器将基于它们能够制定更好的计划,从而推动更好的绩效。

您可以在 AddDate、OrderId 上添加非聚集索引:

CREATE INDEX IX_Orders_AddDate_OrderID
ON dbo.Orders(AddDate, OrderID)

然后重写您的查询:

SELECT       *
FROM Orders
WHERE OrderId >=
           (SELECT MIN(OrderId)
            FROM dbo.Orders
            WHERE AddDate >= @FromDate) AND
      OrderId <=
           (SELECT MAX(OrderId)
            FROM dbo.Orders
            WHERE AddDate <= @ToDate)
ORDER BY AddDate