使用子选择优化查询

时间:2009-01-26 22:26:35

标签: sql sql-server optimization

我正在尝试生成一份销售报告,其中列出了每个产品+给定月份的总销售额。它有点棘手,因为产品价格可能会在整个月内发生变化。例如:

  • 在1月1日至1月15日期间,我公司以每个10美元的价格销售50个小工具
  • 在1月15日至1月31日之间,我的公司再销售50个小部件,每个售价15美元
  • 1月份小工具的总销售额=(50 * 10)+(50 * 15)= 1250美元

此设置在数据库中表示如下:

Sales table
  Sale_ID    ProductID    Sale_Date
  1          1            2009-01-01
  2          1            2009-01-01
  3          1            2009-01-02
             ...
  50         1            2009-01-15
  51         1            2009-01-16
  52         1            2009-01-17
             ...
  100        1            2009-01-31

Prices table
  Product_ID    Sale_Date    Price
  1             2009-01-01   10.00
  1             2009-01-16   15.00

如果在价格表中定义了价格,则会将其应用于使用给定SaleDate中给定ProductID销售的所有产品。

基本上,我正在寻找一个返回数据的查询,如下所示:

Desired output
  Sale_ID    ProductID    Sale_Date     Price
  1          1            2009-01-01    10.00
  2          1            2009-01-01    10.00
  3          1            2009-01-02    10.00
             ...
  50         1            2009-01-15    10.00
  51         1            2009-01-16    15.00
  52         1            2009-01-17    15.00
             ...
  100        1            2009-01-31    15.00

我有以下查询:

SELECT
    Sale_ID,
    Product_ID,
    Sale_Date,
    (
        SELECT TOP 1 Price
        FROM Prices
        WHERE
            Prices.Product_ID = Sales.Product_ID
            AND Prices.Sale_Date < Sales.Sale_Date 
        ORDER BY Prices.Sale_Date DESC
    ) as Price
FROM Sales

这是有效的,但是有一个比嵌套的子选择更有效的查询吗?

在你指出在Sales表中包含“price”会更容易之前,我应该提到架构是由另一个供应商维护的,我无法更改它。如果重要,我正在使用SQL Server 2000。

5 个答案:

答案 0 :(得分:2)

避免这些类型的相关子查询是很好的。这是针对此类案例的经典技巧。

SELECT  
    Sale_ID,  
    Product_ID,  
    Sale_Date,  
    p1.Price  
FROM Sales AS s 
LEFT JOIN Prices AS p1 ON s.ProductID = p1.ProductID  
    AND s.Sale_Date >= p1.Sale_Date  
LEFT JOIN Prices AS p2 ON s.ProductID = p2.ProductID  
    AND s.Sale_Date >= p2.Sale_Date  
    AND p2.Sale_Date > p1.Sale_Date  
WHERE p2.Price IS NULL  -- want this one not to be found

在定价表上使用左外连接作为p2,并查找NULL记录,证明在p1中找到的匹配产品价格记录是销售日期或之前的最新记录。

(我会加入第一次价格匹配,但如果没有,那么很高兴让产品出现,所以你知道有问题。)

答案 1 :(得分:2)

如果您开始存储开始日期和结束日期,或创建包含开始日期和结束日期的视图(您甚至可以创建索引视图),那么您可以大大简化查询。 (如果您确定没有范围重叠)

SELECT
    Sale_ID,
    Product_ID,
    Sale_Date,
    Price
FROM Sales
JOIN Prices on Sale_date > StartDate and Sale_Date <= EndDate  
-- careful not to use between it includes both ends 

注意:

沿着这些方向的技术将允许您使用视图执行此操作。注意,如果你需要为视图编制索引,那么就必须对它进行相当多的调整..

create table t (d datetime)

insert t values(getdate())
insert t values(getdate()+1)
insert t values(getdate()+2)

go
create view myview 
as
select start = isnull(max(t2.d), '1975-1-1'), finish = t1.d  from t t1
left join t t2 on t1.d > t2.d
group by t1.d

select * from myview 

start                   finish
----------------------- -----------------------
1975-01-01 00:00:00.000 2009-01-27 11:12:57.383
2009-01-27 11:12:57.383 2009-01-28 11:12:57.383
2009-01-28 11:12:57.383 2009-01-29 11:12:57.383

答案 2 :(得分:0)

您是否遇到了性能问题,或者您只是期待它们?我会像你一样完全实现这一点,我的双手是从你的架构修改的角度来看。

答案 3 :(得分:0)

我同意肖恩的意见。您编写的代码非常干净且易于理解。如果您遇到性能问题,请花费额外的精力使代码更快。否则,您无缘无故地使代码更复杂。当明智地使用时,嵌套的子选择非常有用。

答案 4 :(得分:-1)

Product_ID和Sale_Date的组合是您的外键。在Product_ID,Sale_Date上尝试选择加入。