在给定日期之前获得最大日期的有效方法

时间:2011-05-23 14:09:46

标签: sql-server nested-queries

假设我有一个名为Transaction的表和另一个名为Price的表。价格保留不同日期的给定资金的价格。每个基金都会在不同的日期添加价格,但他们不会在所有可能的日期都有价格。因此,对于XYZ基金,我可能有5月1日,5月7日和5月13日的价格,基金ABC可能会在5月3日,5月9日和5月11日有价格。

所以现在我正在考虑交易之日基金普遍存在的价格。该交易是在5月10日为XYZ基金进行的。我想要的是当天最新的已知价格,这将是5月7日的价格。

以下是代码:

select d.TransactionID, d.FundCode, d.TransactionDate, v.OfferPrice
from Transaction d
    inner join Price v
        on v.FundCode = d.FundCode
        and v.PriceDate = (
            select max(PriceDate)
            from Price
            where FundCode = v.FundCode
            /* */ and PriceDate < d.TransactionDate 
        )

它有效,但速度非常慢(在现实世界中使用几分钟)。如果我删除带有前导注释的行,则查询非常快(大约2秒),但它会使用每个基金的最新价格,这是错误的。

糟糕的是,与我们使用的其他一些表相比,价格表是微不足道的,而且我不清楚它为什么这么慢。我怀疑违规行迫使SQL Server处理笛卡尔积,但我不知道如何避免它。

我一直希望找到一种更有效的方法来做到这一点,但到目前为止它已经逃过了我。有什么想法吗?

3 个答案:

答案 0 :(得分:5)

有一种方法可以找到具有最大值或最小值的行,这涉及LEFT JOIN到self,而不是更直观,但也可能更昂贵,INNER JOIN到自派的聚合列表。

基本上,该方法使用此模式:

SELECT t.*
FROM t
  LEFT JOIN t AS t2 ON t.key = t2.key
    AND t2.Value > t.Value  /* ">" is when getting maximums; "<" is for minimums */
WHERE t2.key IS NULL

或其NOT EXISTS对应物:

SELECT *
FROM t
WHERE NOT EXISTS (
  SELECT *
  FROM t AS t2
  WHERE t.key = t2.key
    AND t2.Value > t.Value  /* same as above applies to ">" here as well */
)

因此,结果是所有不存在具有相同键且值大于给定值的行的行。

当只有一个表时,上述方法的应用非常简单。但是,当有另一个表时,如何应用它可能不是那么明显,特别是在你的情况下,另一个表使得实际查询不仅仅因为它存在而更加复杂,而且还为我们提供了额外的过滤我们正在寻找的价值,即日期的上限。

所以,这是应用该方法的LEFT JOIN版本时生成的查询的样子:

SELECT
  d.TransactionID,
  d.FundCode,
  d.TransactionDate,
  v.OfferPrice
FROM Transaction d
  INNER JOIN Price v ON v.FundCode = d.FundCode
  LEFT JOIN Price v2 ON v2.FundCode = v.FundCode  /* this and */
    AND v2.PriceDate > v.PriceDate                /* this are where we are applying
                                                       the above method; */
    AND v2.PriceDate < d.TransactionDate          /* and this is where we are limiting
                                                       the maximum value */
WHERE v2.FundCode IS NULL

这是一个与NOT EXISTS类似的解决方案:

SELECT
  d.TransactionID,
  d.FundCode,
  d.TransactionDate,
  v.OfferPrice
FROM Transaction d
  INNER JOIN Price v ON v.FundCode = d.FundCode
  WHERE NOT EXISTS (
    SELECT *
    FROM Price v2
    WHERE v2.FundCode = v.FundCode           /* this and */
      AND v2.PriceDate > v.PriceDate         /* this are where we are applying
                                                the above method; */
      AND v2.PriceDate < d.TransactionDate   /* and this is where we are limiting
                                                the maximum value */
  )

答案 1 :(得分:4)

您没有指定您正在使用的SQL Server版本,但如果您使用的是支持排名函数和CTE查询的版本,我认为您会发现这比使用相关子查询更高效在您的加入声明中。

它的性能应该与Andriy的查询非常相似。根据表格的确切索引地形,一种方法可能比另一种方法稍快。

我倾向于喜欢基于CTE的方法,因为生成的代码更具可读性(在我看来)。希望这有帮助!

;WITH set_gen (TransactionID, OfferPrice, Match_val)
AS
(
    SELECT d.TransactionID, v.OfferPrice, ROW_NUMBER() OVER(PARTITION BY d.TransactionID ORDER BY v.PriceDate ASC) AS Match_val
    FROM Transaction d
        INNER JOIN Price v
            ON v.FundCode = d.FundCode
    WHERE v.PriceDate <= d.TransactionDate
)
SELECT sg.TransactionID, d.FundCode, d.TransactionDate, sg.OfferPrice
FROM Transaction d
    INNER JOIN set_gen sg ON d.TransactionID = sg.TransactionID
WHERE sg.Match_val = 1

答案 2 :(得分:0)

pricedatetransactiondate都被编入索引吗?如果不是,您正在进行表扫描,这可能是导致性能瓶颈的原因。