Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/74.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 查找与最小/最大值关联的行,不带内部循环_Sql_Sql Server_Tsql - Fatal编程技术网

Sql 查找与最小/最大值关联的行,不带内部循环

Sql 查找与最小/最大值关联的行,不带内部循环,sql,sql-server,tsql,Sql,Sql Server,Tsql,我有一个关于T-SQL和SQL Server的问题 假设我有一个包含两列的订单表: ProductId int 客户ID int 日期时间 我想要每个产品的第一个订单日期,因此我执行以下类型的查询: SELECT ProductId, MIN(Date) AS FirstOrder FROM Orders GROUP BY ProductId 我在ProductId上有一个索引,包括CustomerId和Date列,以加快查询速度(IX_Orders)。查询计划看起来像是对IX_Orde

我有一个关于T-SQL和SQL Server的问题

假设我有一个包含两列的订单表:

  • ProductId int
  • 客户ID int
  • 日期时间
我想要每个产品的第一个订单日期,因此我执行以下类型的查询:

SELECT ProductId, MIN(Date) AS FirstOrder 
FROM Orders
GROUP BY ProductId
我在
ProductId
上有一个索引,包括
CustomerId
Date
列,以加快查询速度(
IX_Orders
)。查询计划看起来像是对
IX_Orders
进行非聚集索引扫描,然后是流聚合(由于索引,因此没有排序)

现在我的问题是,我还想检索与每个产品的第一个订单相关联的
CustomerId
(产品26是客户12于25日星期二首次订购的)。棘手的是,我不希望在执行计划中有任何内部循环,因为这意味着表中的每个
ProductId
都需要额外的读取,这是非常低效的

这应该可以使用相同的非聚集索引扫描,然后是流聚合,但是我似乎找不到一个可以这样做的查询。有什么想法吗

谢谢

这是我能想到的最好的方法,不过老实说,我不知道这个查询的性能特征是什么。如果这样做不好,我可能会建议运行两个查询来获取所需的信息

declare @Orders table (
    ProductId int,
    CustomerId int,
    Date datetime
)

insert into @Orders values (1,1,'20090701')
insert into @Orders values (2,1,'20090703')
insert into @Orders values (3,1,'20090702')
insert into @Orders values (1,2,'20090704')
insert into @Orders values (4,2,'20090701')
insert into @Orders values (1,3,'20090706')
insert into @Orders values (2,3,'20090704')
insert into @Orders values (4,3,'20090702')
insert into @Orders values (5,5,'20090703')

select O.* from @Orders O inner join 
(
    select ProductId,
    MIN(Date) MinDate 
    from @Orders 
    group by ProductId
) FO
on FO.ProductId = O.ProductId and FO.MinDate = O.Date

这方面的估计查询计划是无用的,因为我正在用表变量模拟它,但是匿名内部联接应该在子选择上进行优化。

是按ProductId、CutomerId、Date排序的IX_订单,还是按ProductId、Date、CustomerId排序的订单?如果是前者,则改为后者

换句话说,不要使用此选项:

create index IX_Orders on Orders (ProductId, CustomerId, Date) 
改用这个:

create index IX_Orders on Orders (ProductId, Date, CustomerId)
如果你这样做了:

SELECT o1.* 
FROM [Order] o1
JOIN
    (
        SELECT ProductID, Min(Date) as Date
        FROM [Order]
        GROUP BY ProductID
    ) o2
    ON o1.ProductID = o2.ProductID AND o1.Date = o2.Date
ORDER BY ProductID
最终只需对IX_订单进行一次索引扫描,但如果两个客户可以同时订购同一产品,则每个产品可能会有多行。您可以通过使用以下查询克服此问题,但其效率低于第一个查询:

WITH cte AS
(
    SELECT ProductID, CustomerID, Date, 
        ROW_NUMBER() OVER(PARTITION BY ProductID ORDER BY Date ASC) AS row
    FROM [Order]
)
SELECT ProductID, CustomerId, Date
FROM cte
WHERE row = 1
ORDER BY ProductID

这将处理具有重复日期的产品:

DECLARE @Orders table (ProductId int
                      ,CustomerId int
                      ,Date datetime
                      )

INSERT INTO @Orders VALUES (1,1,'20090701')
INSERT INTO @Orders VALUES (2,1,'20090703')
INSERT INTO @Orders VALUES (3,1,'20090702')
INSERT INTO @Orders VALUES (1,2,'20090704')
INSERT INTO @Orders VALUES (4,2,'20090701')
INSERT INTO @Orders VALUES (1,3,'20090706')
INSERT INTO @Orders VALUES (2,3,'20090704')
INSERT INTO @Orders VALUES (4,3,'20090702')
INSERT INTO @Orders VALUES (5,5,'20090703')  --duplicate dates for product #5
INSERT INTO @Orders VALUES (5,1,'20090703')  --duplicate dates for product #5
INSERT INTO @Orders VALUES (5,5,'20090703')  --duplicate dates for product #5

;WITH MinOrders AS
(SELECT
     o.ProductId, o.CustomerId, o.Date
         ,row_number() over(partition by o.ProductId order by o.ProductId,o.CustomerId) AS RankValue
     FROM @Orders o
     INNER JOIN (SELECT
                     ProductId
                         ,MIN(Date) MinDate 
                     FROM @Orders 
                     GROUP BY ProductId
                ) dt ON o.ProductId=dt.ProductId AND o.Date=dt.MinDate
 )
SELECT
    m.ProductId, m.CustomerId, m.Date
    FROM MinOrders  m
    WHERE m.RankValue=1
    ORDER BY m.ProductId, m.CustomerId
这将返回相同的结果,只需使用与上述代码相同的声明和插入:

;WITH MinOrders AS
(SELECT
     o.ProductId, o.CustomerId, o.Date
         ,row_number() over(partition by o.ProductId order by o.ProductId,o.CustomerId) AS RankValue
     FROM @Orders o
 )
SELECT
    m.ProductId, m.CustomerId, m.Date
    FROM MinOrders  m
    WHERE m.RankValue=1
    ORDER BY m.ProductId, m.CustomerId

您可以尝试每个版本,看看哪个运行得更快…

我看不到不执行子查询或窗口功能(如行数、秩)就可以很好地执行此操作的方法,因为最大值只显示在一列中

然而,你不能做得很好

SELECT
    productid, 
    min(date), 
cast(
    substring( 
        min(convert(varchar(23),date,21) + cast(customerid as varchar(20)))
              , 24, 44)
    as int) customerid
from 
    orders
group by
    productid 
这仅在您的客户id少于20位时有效

编辑:
在SQL Server 2005+中添加了group by子句:

SELECT  oo.*
FROM    (
        SELECT  DISTINCT ProductId
        FROM    Orders
        ) od
CROSS APPLY
        (
        SELECT  TOP 1 ProductID, Date, CustomerID
        FROM    Orders oi
        WHERE   oi.ProductID = od.ProductID
        ORDER BY
                Date DESC
        ) oo
名义上,查询的计划包含
嵌套循环

但是,外部循环将使用
索引扫描
流聚合
,内部循环将包含
索引搜索
,用于
产品ID
顶部

事实上,第二个操作几乎是免费的,因为内部循环中使用的索引页很可能位于缓存中,因为它刚刚用于外部循环

以下是对
1000000
行的测试结果(其中
100
不同
):

,而这仅仅是一个
SELECT DISTINCT
查询的结果:

SELECT  od.*
FROM    (
        SELECT  DISTINCT ProductId
        FROM    Orders
        ) od
统计数字如下:

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 1 ms.

(строк обработано: 100)
Table 'Orders'. Scan count 3, logical reads 5648, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 125 ms.
SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 1 ms.

(строк обработано: 100)
Table 'Orders'. Scan count 1, logical reads 5123, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 406 ms,  elapsed time = 415 ms.
正如我们所看到的,性能是相同的,
交叉应用
只需要
400
额外的
逻辑读取
(这很可能永远不会是
物理

不知道如何改进此查询了

这个查询的另一个好处是它可以很好地并行化。您可能会注意到,
CPU
时间是
运行时间的两倍:这是由于我的旧
核心Duo的并行化

一个
4核
CPU
将在一半的时间内完成此查询

使用窗口函数的解决方案不会并行化:

SELECT  od.*
FROM    (
        SELECT  ProductId, Date, CustomerID, ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY Date DESC) AS rn
        FROM    Orders
        ) od
WHERE   rn = 1
,以下是统计数字:

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 1 ms.

(строк обработано: 100)
Table 'Orders'. Scan count 3, logical reads 5648, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 250 ms,  elapsed time = 125 ms.
SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 1 ms.

(строк обработано: 100)
Table 'Orders'. Scan count 1, logical reads 5123, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 406 ms,  elapsed time = 415 ms.

+1:我想你需要在匿名加入中为min(date)定义一个别名;否则,这正是我得到的。最好知道是否有更好的方法。此查询得到错误的答案,因为它在o1和FirstOrder之间的联接中不包含日期您不需要在联接中包含日期。您只需要子查询的产品id,因为它具有与之关联的最小日期。返回实际结果的select返回日期。您的select需要包含FO.MinDate。我从来没有听说过“匿名”联接,我一直使用术语“派生表”。如果同一产品有多行具有相同的最小日期,这将不起作用。试一试,将此代码添加到示例中:插入@Orders值(5,1,'20090703');在@Orders value(5,5,'20090703')中插入,您将在结果集中多次获得产品5。但这不是有效的吗?您需要任何产品的第一个订单的客户ID,但给定数据,如果多个客户在同一天订购同一产品,我认为您应该将它们全部取回。如果客户订购同一产品两次,那么可能不会发生这种情况-在这种情况下,您可以使O.*
不同
。OP表示“我希望每个产品的第一次订购日期”,对我来说,这意味着只列出一次产品。OP还需要订购它的客户。如果OP使用带有实际时间的DATETIME列(而不仅仅是测试数据中的天数),您可能会得到第一个,因为同时两个订单的概率很低,但仍然可能发生。我认为,如果在同一时间订购相同的产品,OP仍然只需要结果集中的一行,但这是我的看法。Msg 8120,级别16,状态1,第51行列'@orders.ProductId'在选择列表中无效,因为它不包含在聚合函数或组中