【问题标题】:Optimizing Query With Subselect使用子选择优化查询
【发布时间】:2009-02-18 07:50:48
【问题描述】:

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

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

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

销售表 Sale_ID 产品ID 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 价格表 产品_ID销售_日期价格 1 2009-01-01 10.00 1 2009-01-16 15.00

在价格表中定义价格后,它将应用于从给定的 SaleDate 开始以给定 ProductID 售出的所有产品。

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

期望的输出 Sale_ID ProductID Sale_Date 价格 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

这可行,但有没有比嵌套子选择更有效的查询?

在您指出在销售表中包含“价格”会更容易之前,我应该提一下,该架构由另一个供应商维护,我无法更改它。万一这很重要,我使用的是 SQL Server 2000。

【问题讨论】:

  • 您在子选择中缺少 Product_ID 匹配,不是吗?还是我只是不明白你的问题?
  • 你是对的,对此感到抱歉:)

标签: sql sql-server optimization


【解决方案1】:

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

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

【讨论】:

  • 缺点是您明确存储了真正的派生日期,从而产生了冗余。并且增加了复杂性来维护它。想象一下更改日期值...
  • @iedorfer,查看视图解决方案...它将这些东西抽象出来,并为您提供一组干净的日期以加入...使其成为索引视图,它有点棘手的一面。
  • +1 用于建议视图。我不确定如何“索引”视图——这是 SQL Server 特有的吗?
  • yerp,索引视图是特定于 sql server 的,但它们有很多限制,所以要让它们运行起来需要相当多的工作。
  • 我发现这种技术非常简单且易于实施。谢谢:)
【解决方案2】:

最好避免这些类型的相关子查询。这是处理此类情况的经典技术。

选择 Sale_ID, 产品_ID, 发售日期, p1.价格 从销售作为 s LEFT JOIN 价格 AS p1 ON s.ProductID = p1.ProductID AND s.Sale_Date >= p1.Sale_Date 左连接价格为 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 -- 不希望找到这个

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

(我会在第一个价格匹配中加入,但如果没有,最好还是让产品显示出来,这样你就知道有问题了。)

【讨论】:

  • 我认为您的意思是摆脱其中的 top-1 子查询。照原样,您有 2 个 FROM。你的条件也是一样的,所以如果 p1 匹配,p2 也会匹配。
  • 好的,谢谢,我剪的太少了,贴的太多了。固定的。并在第二个外部连接中获得了其他日期比较。超频。
  • 很好的查询。如果我知道优化器会以同样的方式处理它(PostgreSQL 会),我个人会将第二个连接加入到“WHERE NOT EXISTS”子查询中,因为我认为这会让你的意图更加清晰。
【解决方案3】:

您是真的遇到了性能问题,还是只是预料到了这些问题?如果从架构修改的角度来看,我的双手是否像你一样被束缚,我会完全按照你的方式实现它。

【讨论】:

    【解决方案4】:

    我同意肖恩的观点。您编写的代码非常干净易懂。如果您遇到性能问题,请加倍努力使代码更快。否则,你会无缘无故地让代码变得更复杂。明智地使用嵌套子选择非常有用。

    【讨论】:

      【解决方案5】:

      Product_ID 和 Sale_Date 的组合是您的外键。尝试对 Product_ID、Sale_Date 进行选择加入。

      【讨论】:

      • 不是那么简单——并不是每个可以进行销售的日期在价格表中都有对应的行。要查找给定日期的价格,您需要找到Sale_Date 等于或早于给定日期的最新价格。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-11
      • 1970-01-01
      • 1970-01-01
      • 2021-04-09
      • 2013-06-26
      相关资源
      最近更新 更多